From eeae8a77ca431e67ba7cb4fd4d5e7129a6ae7a32 Mon Sep 17 00:00:00 2001 From: Arturo Reyes Lopez Date: Mon, 2 Dec 2024 13:12:10 -0700 Subject: [PATCH 1/6] Validation for Quantity Supplied in Fuel Supply. --- backend/lcfs/web/api/fuel_supply/schema.py | 4 ++- .../FuelSupplies/AddEditFuelSupplies.jsx | 26 +++++++++++++++++++ frontend/src/views/FuelSupplies/_schema.jsx | 2 +- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/backend/lcfs/web/api/fuel_supply/schema.py b/backend/lcfs/web/api/fuel_supply/schema.py index 60592dffe..68300b1e0 100644 --- a/backend/lcfs/web/api/fuel_supply/schema.py +++ b/backend/lcfs/web/api/fuel_supply/schema.py @@ -119,7 +119,9 @@ class FuelSupplyCreateUpdateSchema(BaseSchema): fuel_category_id: int end_use_id: Optional[int] = None provision_of_the_act_id: int - quantity: int + quantity: int = Field( + ..., gt=0, description="Quantity supplied must be greater than 0" + ) units: str fuel_type_other: Optional[str] = None fuel_code_id: Optional[int] = None diff --git a/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx b/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx index ec29c0e2f..385c70f68 100644 --- a/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx +++ b/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx @@ -66,6 +66,22 @@ export const AddEditFuelSupplies = () => { } }, [location.state]) + const validateField = (params, field, validationFn, errorMessage, alertRef) => { + const newValue = params.newValue; + + if (params.colDef.field === field) { + if (!validationFn(newValue)) { + alertRef.current?.triggerAlert({ + message: errorMessage, + severity: 'error', + }); + return false; + } + } + + return true; // Proceed with the update + }; + const onGridReady = useCallback( async (params) => { setGridApi(params.api) @@ -150,6 +166,16 @@ export const AddEditFuelSupplies = () => { const onCellEditingStopped = useCallback( async (params) => { + const isValid = validateField( + params, + 'quantity', + (value) => value !== null && !isNaN(value) && value > 0, + 'Quantity supplied must be greater than 0.', + alertRef + ); + + if (!isValid) return; + if (params.oldValue === params.newValue) return params.node.updateData({ diff --git a/frontend/src/views/FuelSupplies/_schema.jsx b/frontend/src/views/FuelSupplies/_schema.jsx index 0dc792d73..83848a97c 100644 --- a/frontend/src/views/FuelSupplies/_schema.jsx +++ b/frontend/src/views/FuelSupplies/_schema.jsx @@ -382,7 +382,7 @@ export const fuelSupplyColDefs = (optionsData, errors, warnings) => [ field: 'quantity', headerComponent: RequiredHeader, headerName: i18n.t('fuelSupply:fuelSupplyColLabels.quantity'), - valueFormatter, + valueFormatter: (params) => valueFormatter({ value: params.value }), cellEditor: NumberEditor, cellEditorParams: { precision: 0, From b4a8d8a4be57c039528337d1bf8916202e95088b Mon Sep 17 00:00:00 2001 From: Arturo Reyes Lopez Date: Mon, 2 Dec 2024 15:59:48 -0700 Subject: [PATCH 2/6] Validation of quantity in LCFS. --- .../lcfs/web/api/notional_transfer/schema.py | 4 ++- backend/lcfs/web/api/transaction/schema.py | 6 +++-- backend/lcfs/web/api/transfer/schema.py | 6 +++-- .../AddEditAllocationAgreements.jsx | 26 +++++++++++++++++++ .../views/AllocationAgreements/_schema.jsx | 3 ++- .../AddEditNotionalTransfers.jsx | 26 +++++++++++++++++++ .../src/views/NotionalTransfers/_schema.jsx | 2 +- 7 files changed, 66 insertions(+), 7 deletions(-) diff --git a/backend/lcfs/web/api/notional_transfer/schema.py b/backend/lcfs/web/api/notional_transfer/schema.py index 5f6571e57..6ca7085ea 100644 --- a/backend/lcfs/web/api/notional_transfer/schema.py +++ b/backend/lcfs/web/api/notional_transfer/schema.py @@ -20,7 +20,9 @@ class NotionalTransferCreateSchema(BaseSchema): address_for_service: str fuel_category: str received_or_transferred: ReceivedOrTransferredEnumSchema - quantity: int + quantity: int = Field( + ..., gt=0, description="Quantity supplied must be greater than 0" + ) notional_transfer_id: Optional[int] = None compliance_report_id: int deleted: Optional[bool] = None diff --git a/backend/lcfs/web/api/transaction/schema.py b/backend/lcfs/web/api/transaction/schema.py index 34a44b441..8bd05856d 100644 --- a/backend/lcfs/web/api/transaction/schema.py +++ b/backend/lcfs/web/api/transaction/schema.py @@ -1,6 +1,6 @@ from typing import Optional, List -from pydantic import ConfigDict +from pydantic import ConfigDict, Field from lcfs.web.api.base import BaseSchema from datetime import datetime from enum import Enum @@ -71,7 +71,9 @@ class TransactionViewSchema(BaseSchema): transaction_type: str from_organization: Optional[str] = None to_organization: str - quantity: int + quantity: int = Field( + ..., gt=0, description="Quantity supplied must be greater than 0" + ) price_per_unit: Optional[float] = None status: str create_date: datetime diff --git a/backend/lcfs/web/api/transfer/schema.py b/backend/lcfs/web/api/transfer/schema.py index 858accf73..4d8826c10 100644 --- a/backend/lcfs/web/api/transfer/schema.py +++ b/backend/lcfs/web/api/transfer/schema.py @@ -3,7 +3,7 @@ from typing import Optional, List from datetime import date, datetime from enum import Enum -from pydantic import ConfigDict +from pydantic import ConfigDict, Field class TransferRecommendationEnumSchema(str, Enum): @@ -48,7 +48,9 @@ class TransferSchema(BaseSchema): from_organization: TransferOrganizationSchema to_organization: TransferOrganizationSchema agreement_date: date - quantity: int + quantity: int = Field( + ..., gt=0, description="Quantity supplied must be greater than 0" + ) price_per_unit: float comments: Optional[List[TransferCommentSchema]] = None from_org_comment: Optional[str] = None diff --git a/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx b/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx index ebf346e27..88ff97d19 100644 --- a/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx +++ b/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx @@ -67,6 +67,22 @@ export const AddEditAllocationAgreements = () => { } }, [location.state]) + const validateField = (params, field, validationFn, errorMessage, alertRef) => { + const newValue = params.newValue; + + if (params.colDef.field === field) { + if (!validationFn(newValue)) { + alertRef.current?.triggerAlert({ + message: errorMessage, + severity: 'error', + }); + return false; + } + } + + return true; // Proceed with the update + }; + const onGridReady = useCallback( async (params) => { setGridApi(params.api) @@ -154,6 +170,16 @@ export const AddEditAllocationAgreements = () => { const onCellEditingStopped = useCallback( async (params) => { + const isValid = validateField( + params, + 'quantity', + (value) => value !== null && !isNaN(value) && value > 0, + 'Quantity must be greater than 0.', + alertRef + ); + + if (!isValid) return; + if (params.oldValue === params.newValue) return params.node.updateData({ diff --git a/frontend/src/views/AllocationAgreements/_schema.jsx b/frontend/src/views/AllocationAgreements/_schema.jsx index c1a878878..e9d6e29d6 100644 --- a/frontend/src/views/AllocationAgreements/_schema.jsx +++ b/frontend/src/views/AllocationAgreements/_schema.jsx @@ -435,7 +435,8 @@ export const allocationAgreementColDefs = (optionsData, errors) => [ headerName: i18n.t( 'allocationAgreement:allocationAgreementColLabels.quantity' ), - valueFormatter, + editor: NumberEditor, + valueFormatter: (params) => valueFormatter({ value: params.value }), cellEditor: NumberEditor, cellEditorParams: { precision: 0, diff --git a/frontend/src/views/NotionalTransfers/AddEditNotionalTransfers.jsx b/frontend/src/views/NotionalTransfers/AddEditNotionalTransfers.jsx index 2df027624..541865995 100644 --- a/frontend/src/views/NotionalTransfers/AddEditNotionalTransfers.jsx +++ b/frontend/src/views/NotionalTransfers/AddEditNotionalTransfers.jsx @@ -47,6 +47,22 @@ export const AddEditNotionalTransfers = () => { } }, [location.state]) + const validateField = (params, field, validationFn, errorMessage, alertRef) => { + const newValue = params.newValue; + + if (params.colDef.field === field) { + if (!validationFn(newValue)) { + alertRef.current?.triggerAlert({ + message: errorMessage, + severity: 'error', + }); + return false; + } + } + + return true; // Proceed with the update + }; + const onGridReady = (params) => { const ensureRowIds = (rows) => { return rows.map((row) => { @@ -96,6 +112,16 @@ export const AddEditNotionalTransfers = () => { const onCellEditingStopped = useCallback( async (params) => { + const isValid = validateField( + params, + 'quantity', + (value) => value !== null && !isNaN(value) && value > 0, + 'Quantity must be greater than 0.', + alertRef + ); + + if (!isValid) return; + if (params.oldValue === params.newValue) return // Initialize updated data with 'pending' status diff --git a/frontend/src/views/NotionalTransfers/_schema.jsx b/frontend/src/views/NotionalTransfers/_schema.jsx index 38673f676..6064debe4 100644 --- a/frontend/src/views/NotionalTransfers/_schema.jsx +++ b/frontend/src/views/NotionalTransfers/_schema.jsx @@ -140,7 +140,7 @@ export const notionalTransferColDefs = (optionsData, errors) => [ min: 0, showStepperButtons: false }, - valueFormatter, + valueFormatter: (params) => valueFormatter({ value: params.value }), cellStyle: (params) => StandardCellErrors(params, errors) } ] From 9879041254e3b8c8875e5453d4d39cb6ca78360f Mon Sep 17 00:00:00 2001 From: Arturo Reyes Lopez Date: Thu, 28 Nov 2024 16:41:27 -0700 Subject: [PATCH 3/6] Zero is not a valid number for quantity supplied --- backend/lcfs/web/api/other_uses/schema.py | 4 ++- .../src/views/OtherUses/AddEditOtherUses.jsx | 26 +++++++++++++++++++ frontend/src/views/OtherUses/_schema.jsx | 3 +-- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/backend/lcfs/web/api/other_uses/schema.py b/backend/lcfs/web/api/other_uses/schema.py index db3e591be..03612276a 100644 --- a/backend/lcfs/web/api/other_uses/schema.py +++ b/backend/lcfs/web/api/other_uses/schema.py @@ -90,7 +90,9 @@ class OtherUsesCreateSchema(BaseSchema): fuel_type: str fuel_category: str provision_of_the_act: str - quantity_supplied: int + quantity_supplied: int = Field( + ..., gt=0, description="Quantity supplied must be greater than 0" + ) units: str expected_use: str fuel_code: Optional[str] = None diff --git a/frontend/src/views/OtherUses/AddEditOtherUses.jsx b/frontend/src/views/OtherUses/AddEditOtherUses.jsx index 08d9f250a..f983bd120 100644 --- a/frontend/src/views/OtherUses/AddEditOtherUses.jsx +++ b/frontend/src/views/OtherUses/AddEditOtherUses.jsx @@ -81,6 +81,22 @@ export const AddEditOtherUses = () => { return ciOfFuel }, []) + const validateField = (params, field, validationFn, errorMessage, alertRef) => { + const newValue = params.newValue; + + if (params.colDef.field === field) { + if (!validationFn(newValue)) { + alertRef.current?.triggerAlert({ + message: errorMessage, + severity: 'error', + }); + return false; + } + } + + return true; // Proceed with the update + }; + const onGridReady = (params) => { const ensureRowIds = (rows) => { return rows.map((row) => { @@ -200,6 +216,16 @@ export const AddEditOtherUses = () => { const onCellEditingStopped = useCallback( async (params) => { + const isValid = validateField( + params, + 'quantitySupplied', + (value) => value !== null && !isNaN(value) && value > 0, + 'Quantity supplied must be greater than 0.', + alertRef + ); + + if (!isValid) return; + if (params.oldValue === params.newValue) return params.data.complianceReportId = complianceReportId params.data.validationStatus = 'pending' diff --git a/frontend/src/views/OtherUses/_schema.jsx b/frontend/src/views/OtherUses/_schema.jsx index 03ce751fb..401b6db61 100644 --- a/frontend/src/views/OtherUses/_schema.jsx +++ b/frontend/src/views/OtherUses/_schema.jsx @@ -209,11 +209,10 @@ export const otherUsesColDefs = (optionsData, errors) => [ headerName: i18n.t('otherUses:otherUsesColLabels.quantitySupplied'), headerComponent: RequiredHeader, cellEditor: NumberEditor, - valueFormatter, + valueFormatter: (params) => valueFormatter({ value: params.value }), type: 'numericColumn', cellEditorParams: { precision: 0, - min: 0, showStepperButtons: false }, cellStyle: (params) => StandardCellErrors(params, errors), From 747e5e4aa147ef5d6b8152db75e4f06851f6491a Mon Sep 17 00:00:00 2001 From: Arturo Reyes Lopez Date: Tue, 3 Dec 2024 18:02:57 -0700 Subject: [PATCH 4/6] Added vitests. --- .../AddEditAllocationAgreements.jsx | 26 +- .../__tests__/AllocationAgreements.test.jsx | 189 ++++++++++++++ .../FuelSupplies/AddEditFuelSupplies.jsx | 4 +- .../__tests__/FuelSupplies.test.jsx | 232 ++++++++++++++++++ .../AddEditNotionalTransfers.jsx | 4 +- .../AddEditNotionalTransfer.test.jsx | 136 ++++++++++ 6 files changed, 577 insertions(+), 14 deletions(-) create mode 100644 frontend/src/views/AllocationAgreements/__tests__/AllocationAgreements.test.jsx create mode 100644 frontend/src/views/FuelSupplies/__tests__/FuelSupplies.test.jsx create mode 100644 frontend/src/views/NotionalTransfers/__tests__/AddEditNotionalTransfer.test.jsx diff --git a/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx b/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx index 88ff97d19..843d41a7c 100644 --- a/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx +++ b/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx @@ -65,23 +65,29 @@ export const AddEditAllocationAgreements = () => { severity: location.state.severity || 'info' }) } - }, [location.state]) + }, [location.state?.message, location.state?.severity]) - const validateField = (params, field, validationFn, errorMessage, alertRef) => { - const newValue = params.newValue; + const validateField = ( + params, + field, + validationFn, + errorMessage, + alertRef + ) => { + const newValue = params.newValue if (params.colDef.field === field) { if (!validationFn(newValue)) { alertRef.current?.triggerAlert({ message: errorMessage, - severity: 'error', - }); - return false; + severity: 'error' + }) + return false } } - return true; // Proceed with the update - }; + return true // Proceed with the update + } const onGridReady = useCallback( async (params) => { @@ -176,9 +182,9 @@ export const AddEditAllocationAgreements = () => { (value) => value !== null && !isNaN(value) && value > 0, 'Quantity must be greater than 0.', alertRef - ); + ) - if (!isValid) return; + if (!isValid) return if (params.oldValue === params.newValue) return diff --git a/frontend/src/views/AllocationAgreements/__tests__/AllocationAgreements.test.jsx b/frontend/src/views/AllocationAgreements/__tests__/AllocationAgreements.test.jsx new file mode 100644 index 000000000..eb53f33cb --- /dev/null +++ b/frontend/src/views/AllocationAgreements/__tests__/AllocationAgreements.test.jsx @@ -0,0 +1,189 @@ +import React from 'react' +import { render, screen, fireEvent, waitFor } from '@testing-library/react' +import { vi } from 'vitest' +import { AddEditAllocationAgreements } from '../AddAllocationAgreements' +import * as useGetAllocationAgreements from '@/hooks/useAllocationAgreement' +import * as useAllocationAgreementOptions from '@/hooks/useAllocationAgreement' +import * as useSaveAllocationAgreement from '@/hooks/useAllocationAgreement' +import { wrapper } from '@/tests/utils/wrapper' + +vi.mock('@react-keycloak/web', () => ({ + ReactKeycloakProvider: ({ children }) => children, + useKeycloak: () => ({ + keycloak: { + authenticated: true, + login: vi.fn(), + logout: vi.fn(), + register: vi.fn() + }, + initialized: true + }) +})) + +// Mock useApiService +vi.mock('@/services/useApiService', () => ({ + default: vi.fn(() => ({ + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn() + })), + useApiService: vi.fn(() => ({ + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn() + })) +})) + +// Mock react-router-dom +const mockUseParams = vi.fn() +const mockUseLocation = vi.fn(() => ({ + state: { message: 'Test message', severity: 'info' } +})) +const mockUseNavigate = vi.fn() +const mockHasRoles = vi.fn() + +vi.mock('react-router-dom', () => ({ + ...vi.importActual('react-router-dom'), + useParams: () => ({ + complianceReportId: '123', + compliancePeriod: '2023' + }), + useLocation: () => mockUseLocation, + useNavigate: () => mockUseNavigate +})) + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key) => key + }) +})) + + +describe('AddEditAllocationAgreement', () => { + const setupMocks = (overrides = {}) => { + const defaultMocks = { + useParams: { compliancePeriod: '2023', complianceReportId: '123' }, + useLocation: { state: {} } + } + + const mocks = { ...defaultMocks, ...overrides } + mockUseParams.mockReturnValue(mocks.useParams) + mockUseLocation.mockReturnValue(mocks.useLocation) + } + + beforeEach(() => { + vi.resetAllMocks() + setupMocks() + + // Reapply mocks to ensure they are correctly initialized + vi.mock('@/hooks/useAllocationAgreement', () => ({ + useAllocationAgreementOptions: vi.fn(() => ({ + data: { + allocationTransactionTypes: [ + { + allocationTransactionTypeId: 1, + type: "Purchased" + }, + { + allocationTransactionTypeId: 2, + type: "Sold" + } + ], + fuelTypes: [ + { + fuelTypeId: 1, + fuelType: "Biodiesel", + defaultCarbonIntensity: 100.21, + units: "L", + unrecognized: false, + fuelCategories: [ + { + fuelCategoryId: 2, + category: "Diesel", + defaultAndPrescribedCi: 100.21 + } + ], + fuelCodes: [ + { + fuelCodeId: 2, + fuelCode: "BCLCF124.4", + carbonIntensity: 3.62 + } + ], + provisionOfTheAct: [ + { + provisionOfTheActId: 2, + name: "Fuel code - section 19 (b) (i)" + }, + { + provisionOfTheActId: 3, + name: "Default carbon intensity - section 19 (b) (ii)" + } + ] + } + ], + provisionsOfTheAct: [ + { + provisionOfTheActId: 3, + name: "Default carbon intensity - section 19 (b) (ii)" + } + ], + fuelCodes: [ + { + fuelCodeId: 1, + fuelCode: "BCLCF102.5", + carbonIntensity: 37.21 + } + ], + unitsOfMeasure: [ + "L" + ] + }, + isLoading: false, + isFetched: true + })), + useGetAllocationAgreements: vi.fn(() => ({ + data: { allocationAgreements: [], pagination: {} }, + isLoading: false + })), + useSaveAllocationAgreement: vi.fn(() => ({ + mutateAsync: vi.fn() + })) + })) + }) + + it('renders the component', async () => { + render(, { wrapper }) + await waitFor(() => { + expect( + screen.getByText(/Enter allocation agreement details below/i) + ).toBeInTheDocument() + }) + }) + + it('should show error for 0 quantity', () => { + render(); + const quantityInput = screen.getByLabelText('Quantity'); + fireEvent.change(quantityInput, { target: { value: '0' } }); + fireEvent.blur(quantityInput); + expect(screen.getByText('Quantity must be greater than 0.')).toBeInTheDocument(); + }); + + it('should show error for empty quantity', () => { + render(); + const quantityInput = screen.getByLabelText('Quantity'); + fireEvent.change(quantityInput, { target: { value: '' } }); + fireEvent.blur(quantityInput); + expect(screen.getByText('Quantity must be greater than 0.')).toBeInTheDocument(); + }); + + it('should not show error for valid quantity', () => { + render(); + const quantityInput = screen.getByLabelText('Quantity'); + fireEvent.change(quantityInput, { target: { value: '10' } }); + fireEvent.blur(quantityInput); + expect(screen.queryByText('Quantity must be greater than 0.')).not.toBeInTheDocument(); + }); +}) diff --git a/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx b/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx index 385c70f68..f78eb2ab3 100644 --- a/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx +++ b/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx @@ -58,13 +58,13 @@ export const AddEditFuelSupplies = () => { ) useEffect(() => { - if (location.state?.message) { + if (location?.state?.message) { alertRef.current?.triggerAlert({ message: location.state.message, severity: location.state.severity || 'info' }) } - }, [location.state]) + }, [location?.state?.message, location?.state?.severity]); const validateField = (params, field, validationFn, errorMessage, alertRef) => { const newValue = params.newValue; diff --git a/frontend/src/views/FuelSupplies/__tests__/FuelSupplies.test.jsx b/frontend/src/views/FuelSupplies/__tests__/FuelSupplies.test.jsx new file mode 100644 index 000000000..cc051c66f --- /dev/null +++ b/frontend/src/views/FuelSupplies/__tests__/FuelSupplies.test.jsx @@ -0,0 +1,232 @@ +import React from 'react' +import { render, screen, fireEvent, waitFor } from '@testing-library/react' +import { vi } from 'vitest' +import { AddEditFuelSupplies } from '../AddEditFuelSupplies' +import * as useFuelSupplyHooks from '@/hooks/useFuelSupply' +import { wrapper } from '@/tests/utils/wrapper' + +vi.mock('@react-keycloak/web', () => ({ + ReactKeycloakProvider: ({ children }) => children, + useKeycloak: () => ({ + keycloak: { + authenticated: true, + login: vi.fn(), + logout: vi.fn(), + register: vi.fn() + }, + initialized: true + }) +})) + +// Mock react-router-dom +const mockUseParams = vi.fn() +const mockUseLocation = vi.fn(() => ({ + state: { message: 'Test message', severity: 'info' } +})) +const mockUseNavigate = vi.fn() + +vi.mock('react-router-dom', () => ({ + ...vi.importActual('react-router-dom'), + useParams: () => ({ + complianceReportId: '123', + compliancePeriod: '2023' + }), + useLocation: () => mockUseLocation(), + useNavigate: () => mockUseNavigate +})) + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key) => key + }) +})) + +// Mock hooks +vi.mock('@/hooks/useFuelSupply', () => ({ + useFuelSupplyOptions: vi.fn(() => ({ + data: { fuelTypes: [ + { + fuelTypeId: 2, + fuelType: 'CNG', + fossilDerived: false, + defaultCarbonIntensity: 63.91, + units: 'm³', + unrecognized: false, + }, + { + fuelTypeId: 3, + fuelType: 'Electric', + defaultCarbonIntensity: 12.14, + units: 'kWh', + unrecognized: false, + }, + ] }, + isLoading: false, + isFetched: true, + })), + useGetFuelSupplies: { + data: { fuelSupplies: [ + { + fuelSupplyId: 1, + complianceReportId: 2, + groupUuid: "fc44368c-ca60-4654-8f3d-32b55aa16245", + version: 0, + userType: "SUPPLIER", + actionType: "CREATE", + fuelTypeId: 3, + fuelType: { + fuelTypeId: 3, + fuelType: "Electricity", + fossilDerived: false, + defaultCarbonIntensity: 12.14, + units: "kWh" + }, + fuelCategoryId: 1, + fuelCategory: { + fuelCategoryId: 1, + category: "Gasoline" + }, + endUseId: 1, + endUseType: { + endUseTypeId: 1, + type: "Light duty motor vehicles" + }, + provisionOfTheActId: 3, + provisionOfTheAct: { + provisionOfTheActId: 3, + name: "Default carbon intensity - section 19 (b) (ii)" + }, + quantity: 1000000, + units: "kWh" + }, + { + fuelSupplyId: 2, + complianceReportId: 2, + groupUuid: "0f571126-43ae-43e7-b04b-705a22a2cbaf", + version: 0, + userType: "SUPPLIER", + actionType: "CREATE", + fuelTypeId: 3, + fuelType: { + fuelTypeId: 3, + fuelType: "Electricity", + fossilDerived: false, + defaultCarbonIntensity: 12.14, + units: "kWh" + }, + fuelCategoryId: 1, + fuelCategory: { + fuelCategoryId: 1, + category: "Gasoline" + }, + endUseId: 2, + endUseType: { + endUseTypeId: 2, + type: "Other or unknown" + }, + provisionOfTheActId: 3, + provisionOfTheAct: { + provisionOfTheActId: 3, + name: "Default carbon intensity - section 19 (b) (ii)" + }, + quantity: 100000, + units: "kWh" + } + ] + , + pagination: { + page: 1, + size: 10, + total: 2, + totalPages: 1, + }, }, + isLoading: false + }, + useSaveFuelSupply: vi.fn(() => ({ + mutateAsync: vi.fn(), + })), +})); + +describe('AddEditFuelSupplies', () => { + beforeEach(() => { + vi.resetAllMocks() + }) + + it('renders the component with no initial data', async () => { + render(, { wrapper }) + + await waitFor(() => { + expect( + screen.getByText(/Add new supply of fuel/i) + ).toBeInTheDocument() + }) + }) + + it('should show error for 0 quantity', async () => { + render(, { wrapper }) + + const quantityInput = screen.getByLabelText(/quantity/i) + fireEvent.change(quantityInput, { target: { value: '0' } }) + fireEvent.blur(quantityInput) + + await waitFor(() => { + expect( + screen.getByText(/quantity supplied must be greater than 0./i) + ).toBeInTheDocument() + }) + }) + + it('should show error for empty quantity', async () => { + render(, { wrapper }) + + const quantityInput = screen.getByLabelText(/quantity/i) + fireEvent.change(quantityInput, { target: { value: '' } }) + fireEvent.blur(quantityInput) + + await waitFor(() => { + expect( + screen.getByText(/quantity supplied must be greater than 0./i) + ).toBeInTheDocument() + }) + }) + + it('should not show error for valid quantity', async () => { + render(, { wrapper }) + + const quantityInput = screen.getByLabelText(/quantity/i) + fireEvent.change(quantityInput, { target: { value: '10' } }) + fireEvent.blur(quantityInput) + + await waitFor(() => { + expect( + screen.queryByText(/quantity supplied must be greater than 0./i) + ).not.toBeInTheDocument() + }) + }) + + it('displays an error message when row update fails', async () => { + const mockMutateAsync = vi.fn().mockRejectedValueOnce({ + response: { + data: { + errors: [{ fields: ['quantity'], message: 'Invalid quantity' }] + } + } + }) + + vi.mocked(useFuelSupplyHooks.useSaveFuelSupply).mockReturnValueOnce({ + mutateAsync: mockMutateAsync + }) + + render(, { wrapper }) + + const quantityInput = screen.getByLabelText(/quantity/i) + fireEvent.change(quantityInput, { target: { value: '-5' } }) + fireEvent.blur(quantityInput) + + await waitFor(() => { + expect( + screen.getByText(/error updating row: invalid quantity/i) + ).toBeInTheDocument() + }) + }) +}) diff --git a/frontend/src/views/NotionalTransfers/AddEditNotionalTransfers.jsx b/frontend/src/views/NotionalTransfers/AddEditNotionalTransfers.jsx index 541865995..ad5aaee2b 100644 --- a/frontend/src/views/NotionalTransfers/AddEditNotionalTransfers.jsx +++ b/frontend/src/views/NotionalTransfers/AddEditNotionalTransfers.jsx @@ -39,13 +39,13 @@ export const AddEditNotionalTransfers = () => { const navigate = useNavigate() useEffect(() => { - if (location.state?.message) { + if (location?.state?.message) { alertRef.triggerAlert({ message: location.state.message, severity: location.state.severity || 'info' }) } - }, [location.state]) + }, [location?.state?.message, location?.state?.severity]); const validateField = (params, field, validationFn, errorMessage, alertRef) => { const newValue = params.newValue; diff --git a/frontend/src/views/NotionalTransfers/__tests__/AddEditNotionalTransfer.test.jsx b/frontend/src/views/NotionalTransfers/__tests__/AddEditNotionalTransfer.test.jsx new file mode 100644 index 000000000..18145bca7 --- /dev/null +++ b/frontend/src/views/NotionalTransfers/__tests__/AddEditNotionalTransfer.test.jsx @@ -0,0 +1,136 @@ +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { vi } from 'vitest'; +import { AddEditNotionalTransfers } from '../AddEditNotionalTransfers'; +import * as useNotionalTransfer from '@/hooks/useNotionalTransfer'; +import { wrapper } from '@/tests/utils/wrapper'; + +vi.mock('@react-keycloak/web', () => ({ + ReactKeycloakProvider: ({ children }) => children, + useKeycloak: () => ({ + keycloak: { + authenticated: true, + login: vi.fn(), + logout: vi.fn(), + register: vi.fn(), + }, + initialized: true, + }), +})); + +// Mock react-router-dom +const mockUseParams = vi.fn(); +const mockUseLocation = vi.fn(() => ({ + state: { message: 'Test message', severity: 'info' }, +})); +const mockUseNavigate = vi.fn(); + +vi.mock('react-router-dom', () => ({ + ...vi.importActual('react-router-dom'), + useParams: () => ({ + complianceReportId: '123', + compliancePeriod: '2023', + }), + useLocation: () => mockUseLocation(), + useNavigate: () => mockUseNavigate, +})); + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key) => key, + }), +})); + +vi.mock('@/hooks/useNotionalTransfer', () => ({ + useNotionalTransferOptions: vi.fn(() => ({ + data: { + fuelCategories: [ + { + fuelCategoryId: 1, + category: "Gasoline", + description: "Gasoline" + }, + { + fuelCategoryId: 2, + category: "Diesel", + description: "Diesel" + }, + { + fuelCategoryId: 3, + category: "Jet fuel", + description: "Jet fuel" + } + ], + receivedOrTransferred: [ + "Received", + "Transferred" + ] + }, + isLoading: false, + isFetched: true, + })), + useGetAllNotionalTransfers: vi.fn(() => ({ + data: { + notionalTransfers: [] + }, + isLoading: false, + })), + useSaveNotionalTransfer: vi.fn(() => ({ + mutateAsync: vi.fn(), // Properly mock mutateAsync + })), + })); + +describe('AddEditNotionalTransfers', () => { + beforeEach(() => { + vi.resetAllMocks(); + + vi.spyOn(useNotionalTransfer, 'useSaveNotionalTransfer').mockReturnValue({ + mutateAsync: vi.fn(), // Ensure mutateAsync is mocked + }); +}); + it('renders the component successfully', async () => { + render(, { wrapper }); + + await waitFor(() => { + expect( + screen.getByText(/Add new notional transfer(s)/i) + ).toBeInTheDocument(); + }); + }); + + it('shows an error for 0 quantity', async () => { + render(, { wrapper }); + + const quantityInput = screen.getByLabelText(/quantity/i); + fireEvent.change(quantityInput, { target: { value: '0' } }); + fireEvent.blur(quantityInput); + + await waitFor(() => { + expect(screen.getByText(/quantity must be greater than 0./i)).toBeInTheDocument(); + }); + }); + + it('shows an error for empty quantity', async () => { + render(, { wrapper }); + + const quantityInput = screen.getByLabelText(/quantity/i); + fireEvent.change(quantityInput, { target: { value: '' } }); + fireEvent.blur(quantityInput); + + await waitFor(() => { + expect(screen.getByText(/quantity must be greater than 0./i)).toBeInTheDocument(); + }); + }); + + it('does not show an error for a valid quantity', async () => { + render(, { wrapper }); + + const quantityInput = screen.getByLabelText(/quantity/i); + fireEvent.change(quantityInput, { target: { value: '10' } }); + fireEvent.blur(quantityInput); + + await waitFor(() => { + expect(screen.queryByText(/quantity must be greater than 0./i)).not.toBeInTheDocument(); + }); + }); +}); From 5ff19a3832219ba3f81adf5e466371352ce6c6ef Mon Sep 17 00:00:00 2001 From: Arturo Reyes Lopez Date: Wed, 11 Dec 2024 11:33:41 -0700 Subject: [PATCH 5/6] removing vitests --- .../__tests__/AllocationAgreements.test.jsx | 189 -------------- .../__tests__/FuelSupplies.test.jsx | 232 ------------------ .../AddEditNotionalTransfer.test.jsx | 136 ---------- 3 files changed, 557 deletions(-) delete mode 100644 frontend/src/views/AllocationAgreements/__tests__/AllocationAgreements.test.jsx delete mode 100644 frontend/src/views/FuelSupplies/__tests__/FuelSupplies.test.jsx delete mode 100644 frontend/src/views/NotionalTransfers/__tests__/AddEditNotionalTransfer.test.jsx diff --git a/frontend/src/views/AllocationAgreements/__tests__/AllocationAgreements.test.jsx b/frontend/src/views/AllocationAgreements/__tests__/AllocationAgreements.test.jsx deleted file mode 100644 index eb53f33cb..000000000 --- a/frontend/src/views/AllocationAgreements/__tests__/AllocationAgreements.test.jsx +++ /dev/null @@ -1,189 +0,0 @@ -import React from 'react' -import { render, screen, fireEvent, waitFor } from '@testing-library/react' -import { vi } from 'vitest' -import { AddEditAllocationAgreements } from '../AddAllocationAgreements' -import * as useGetAllocationAgreements from '@/hooks/useAllocationAgreement' -import * as useAllocationAgreementOptions from '@/hooks/useAllocationAgreement' -import * as useSaveAllocationAgreement from '@/hooks/useAllocationAgreement' -import { wrapper } from '@/tests/utils/wrapper' - -vi.mock('@react-keycloak/web', () => ({ - ReactKeycloakProvider: ({ children }) => children, - useKeycloak: () => ({ - keycloak: { - authenticated: true, - login: vi.fn(), - logout: vi.fn(), - register: vi.fn() - }, - initialized: true - }) -})) - -// Mock useApiService -vi.mock('@/services/useApiService', () => ({ - default: vi.fn(() => ({ - get: vi.fn(), - post: vi.fn(), - put: vi.fn(), - delete: vi.fn() - })), - useApiService: vi.fn(() => ({ - get: vi.fn(), - post: vi.fn(), - put: vi.fn(), - delete: vi.fn() - })) -})) - -// Mock react-router-dom -const mockUseParams = vi.fn() -const mockUseLocation = vi.fn(() => ({ - state: { message: 'Test message', severity: 'info' } -})) -const mockUseNavigate = vi.fn() -const mockHasRoles = vi.fn() - -vi.mock('react-router-dom', () => ({ - ...vi.importActual('react-router-dom'), - useParams: () => ({ - complianceReportId: '123', - compliancePeriod: '2023' - }), - useLocation: () => mockUseLocation, - useNavigate: () => mockUseNavigate -})) - -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ - t: (key) => key - }) -})) - - -describe('AddEditAllocationAgreement', () => { - const setupMocks = (overrides = {}) => { - const defaultMocks = { - useParams: { compliancePeriod: '2023', complianceReportId: '123' }, - useLocation: { state: {} } - } - - const mocks = { ...defaultMocks, ...overrides } - mockUseParams.mockReturnValue(mocks.useParams) - mockUseLocation.mockReturnValue(mocks.useLocation) - } - - beforeEach(() => { - vi.resetAllMocks() - setupMocks() - - // Reapply mocks to ensure they are correctly initialized - vi.mock('@/hooks/useAllocationAgreement', () => ({ - useAllocationAgreementOptions: vi.fn(() => ({ - data: { - allocationTransactionTypes: [ - { - allocationTransactionTypeId: 1, - type: "Purchased" - }, - { - allocationTransactionTypeId: 2, - type: "Sold" - } - ], - fuelTypes: [ - { - fuelTypeId: 1, - fuelType: "Biodiesel", - defaultCarbonIntensity: 100.21, - units: "L", - unrecognized: false, - fuelCategories: [ - { - fuelCategoryId: 2, - category: "Diesel", - defaultAndPrescribedCi: 100.21 - } - ], - fuelCodes: [ - { - fuelCodeId: 2, - fuelCode: "BCLCF124.4", - carbonIntensity: 3.62 - } - ], - provisionOfTheAct: [ - { - provisionOfTheActId: 2, - name: "Fuel code - section 19 (b) (i)" - }, - { - provisionOfTheActId: 3, - name: "Default carbon intensity - section 19 (b) (ii)" - } - ] - } - ], - provisionsOfTheAct: [ - { - provisionOfTheActId: 3, - name: "Default carbon intensity - section 19 (b) (ii)" - } - ], - fuelCodes: [ - { - fuelCodeId: 1, - fuelCode: "BCLCF102.5", - carbonIntensity: 37.21 - } - ], - unitsOfMeasure: [ - "L" - ] - }, - isLoading: false, - isFetched: true - })), - useGetAllocationAgreements: vi.fn(() => ({ - data: { allocationAgreements: [], pagination: {} }, - isLoading: false - })), - useSaveAllocationAgreement: vi.fn(() => ({ - mutateAsync: vi.fn() - })) - })) - }) - - it('renders the component', async () => { - render(, { wrapper }) - await waitFor(() => { - expect( - screen.getByText(/Enter allocation agreement details below/i) - ).toBeInTheDocument() - }) - }) - - it('should show error for 0 quantity', () => { - render(); - const quantityInput = screen.getByLabelText('Quantity'); - fireEvent.change(quantityInput, { target: { value: '0' } }); - fireEvent.blur(quantityInput); - expect(screen.getByText('Quantity must be greater than 0.')).toBeInTheDocument(); - }); - - it('should show error for empty quantity', () => { - render(); - const quantityInput = screen.getByLabelText('Quantity'); - fireEvent.change(quantityInput, { target: { value: '' } }); - fireEvent.blur(quantityInput); - expect(screen.getByText('Quantity must be greater than 0.')).toBeInTheDocument(); - }); - - it('should not show error for valid quantity', () => { - render(); - const quantityInput = screen.getByLabelText('Quantity'); - fireEvent.change(quantityInput, { target: { value: '10' } }); - fireEvent.blur(quantityInput); - expect(screen.queryByText('Quantity must be greater than 0.')).not.toBeInTheDocument(); - }); -}) diff --git a/frontend/src/views/FuelSupplies/__tests__/FuelSupplies.test.jsx b/frontend/src/views/FuelSupplies/__tests__/FuelSupplies.test.jsx deleted file mode 100644 index cc051c66f..000000000 --- a/frontend/src/views/FuelSupplies/__tests__/FuelSupplies.test.jsx +++ /dev/null @@ -1,232 +0,0 @@ -import React from 'react' -import { render, screen, fireEvent, waitFor } from '@testing-library/react' -import { vi } from 'vitest' -import { AddEditFuelSupplies } from '../AddEditFuelSupplies' -import * as useFuelSupplyHooks from '@/hooks/useFuelSupply' -import { wrapper } from '@/tests/utils/wrapper' - -vi.mock('@react-keycloak/web', () => ({ - ReactKeycloakProvider: ({ children }) => children, - useKeycloak: () => ({ - keycloak: { - authenticated: true, - login: vi.fn(), - logout: vi.fn(), - register: vi.fn() - }, - initialized: true - }) -})) - -// Mock react-router-dom -const mockUseParams = vi.fn() -const mockUseLocation = vi.fn(() => ({ - state: { message: 'Test message', severity: 'info' } -})) -const mockUseNavigate = vi.fn() - -vi.mock('react-router-dom', () => ({ - ...vi.importActual('react-router-dom'), - useParams: () => ({ - complianceReportId: '123', - compliancePeriod: '2023' - }), - useLocation: () => mockUseLocation(), - useNavigate: () => mockUseNavigate -})) - -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ - t: (key) => key - }) -})) - -// Mock hooks -vi.mock('@/hooks/useFuelSupply', () => ({ - useFuelSupplyOptions: vi.fn(() => ({ - data: { fuelTypes: [ - { - fuelTypeId: 2, - fuelType: 'CNG', - fossilDerived: false, - defaultCarbonIntensity: 63.91, - units: 'm³', - unrecognized: false, - }, - { - fuelTypeId: 3, - fuelType: 'Electric', - defaultCarbonIntensity: 12.14, - units: 'kWh', - unrecognized: false, - }, - ] }, - isLoading: false, - isFetched: true, - })), - useGetFuelSupplies: { - data: { fuelSupplies: [ - { - fuelSupplyId: 1, - complianceReportId: 2, - groupUuid: "fc44368c-ca60-4654-8f3d-32b55aa16245", - version: 0, - userType: "SUPPLIER", - actionType: "CREATE", - fuelTypeId: 3, - fuelType: { - fuelTypeId: 3, - fuelType: "Electricity", - fossilDerived: false, - defaultCarbonIntensity: 12.14, - units: "kWh" - }, - fuelCategoryId: 1, - fuelCategory: { - fuelCategoryId: 1, - category: "Gasoline" - }, - endUseId: 1, - endUseType: { - endUseTypeId: 1, - type: "Light duty motor vehicles" - }, - provisionOfTheActId: 3, - provisionOfTheAct: { - provisionOfTheActId: 3, - name: "Default carbon intensity - section 19 (b) (ii)" - }, - quantity: 1000000, - units: "kWh" - }, - { - fuelSupplyId: 2, - complianceReportId: 2, - groupUuid: "0f571126-43ae-43e7-b04b-705a22a2cbaf", - version: 0, - userType: "SUPPLIER", - actionType: "CREATE", - fuelTypeId: 3, - fuelType: { - fuelTypeId: 3, - fuelType: "Electricity", - fossilDerived: false, - defaultCarbonIntensity: 12.14, - units: "kWh" - }, - fuelCategoryId: 1, - fuelCategory: { - fuelCategoryId: 1, - category: "Gasoline" - }, - endUseId: 2, - endUseType: { - endUseTypeId: 2, - type: "Other or unknown" - }, - provisionOfTheActId: 3, - provisionOfTheAct: { - provisionOfTheActId: 3, - name: "Default carbon intensity - section 19 (b) (ii)" - }, - quantity: 100000, - units: "kWh" - } - ] - , - pagination: { - page: 1, - size: 10, - total: 2, - totalPages: 1, - }, }, - isLoading: false - }, - useSaveFuelSupply: vi.fn(() => ({ - mutateAsync: vi.fn(), - })), -})); - -describe('AddEditFuelSupplies', () => { - beforeEach(() => { - vi.resetAllMocks() - }) - - it('renders the component with no initial data', async () => { - render(, { wrapper }) - - await waitFor(() => { - expect( - screen.getByText(/Add new supply of fuel/i) - ).toBeInTheDocument() - }) - }) - - it('should show error for 0 quantity', async () => { - render(, { wrapper }) - - const quantityInput = screen.getByLabelText(/quantity/i) - fireEvent.change(quantityInput, { target: { value: '0' } }) - fireEvent.blur(quantityInput) - - await waitFor(() => { - expect( - screen.getByText(/quantity supplied must be greater than 0./i) - ).toBeInTheDocument() - }) - }) - - it('should show error for empty quantity', async () => { - render(, { wrapper }) - - const quantityInput = screen.getByLabelText(/quantity/i) - fireEvent.change(quantityInput, { target: { value: '' } }) - fireEvent.blur(quantityInput) - - await waitFor(() => { - expect( - screen.getByText(/quantity supplied must be greater than 0./i) - ).toBeInTheDocument() - }) - }) - - it('should not show error for valid quantity', async () => { - render(, { wrapper }) - - const quantityInput = screen.getByLabelText(/quantity/i) - fireEvent.change(quantityInput, { target: { value: '10' } }) - fireEvent.blur(quantityInput) - - await waitFor(() => { - expect( - screen.queryByText(/quantity supplied must be greater than 0./i) - ).not.toBeInTheDocument() - }) - }) - - it('displays an error message when row update fails', async () => { - const mockMutateAsync = vi.fn().mockRejectedValueOnce({ - response: { - data: { - errors: [{ fields: ['quantity'], message: 'Invalid quantity' }] - } - } - }) - - vi.mocked(useFuelSupplyHooks.useSaveFuelSupply).mockReturnValueOnce({ - mutateAsync: mockMutateAsync - }) - - render(, { wrapper }) - - const quantityInput = screen.getByLabelText(/quantity/i) - fireEvent.change(quantityInput, { target: { value: '-5' } }) - fireEvent.blur(quantityInput) - - await waitFor(() => { - expect( - screen.getByText(/error updating row: invalid quantity/i) - ).toBeInTheDocument() - }) - }) -}) diff --git a/frontend/src/views/NotionalTransfers/__tests__/AddEditNotionalTransfer.test.jsx b/frontend/src/views/NotionalTransfers/__tests__/AddEditNotionalTransfer.test.jsx deleted file mode 100644 index 18145bca7..000000000 --- a/frontend/src/views/NotionalTransfers/__tests__/AddEditNotionalTransfer.test.jsx +++ /dev/null @@ -1,136 +0,0 @@ -import React from 'react'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { vi } from 'vitest'; -import { AddEditNotionalTransfers } from '../AddEditNotionalTransfers'; -import * as useNotionalTransfer from '@/hooks/useNotionalTransfer'; -import { wrapper } from '@/tests/utils/wrapper'; - -vi.mock('@react-keycloak/web', () => ({ - ReactKeycloakProvider: ({ children }) => children, - useKeycloak: () => ({ - keycloak: { - authenticated: true, - login: vi.fn(), - logout: vi.fn(), - register: vi.fn(), - }, - initialized: true, - }), -})); - -// Mock react-router-dom -const mockUseParams = vi.fn(); -const mockUseLocation = vi.fn(() => ({ - state: { message: 'Test message', severity: 'info' }, -})); -const mockUseNavigate = vi.fn(); - -vi.mock('react-router-dom', () => ({ - ...vi.importActual('react-router-dom'), - useParams: () => ({ - complianceReportId: '123', - compliancePeriod: '2023', - }), - useLocation: () => mockUseLocation(), - useNavigate: () => mockUseNavigate, -})); - -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ - t: (key) => key, - }), -})); - -vi.mock('@/hooks/useNotionalTransfer', () => ({ - useNotionalTransferOptions: vi.fn(() => ({ - data: { - fuelCategories: [ - { - fuelCategoryId: 1, - category: "Gasoline", - description: "Gasoline" - }, - { - fuelCategoryId: 2, - category: "Diesel", - description: "Diesel" - }, - { - fuelCategoryId: 3, - category: "Jet fuel", - description: "Jet fuel" - } - ], - receivedOrTransferred: [ - "Received", - "Transferred" - ] - }, - isLoading: false, - isFetched: true, - })), - useGetAllNotionalTransfers: vi.fn(() => ({ - data: { - notionalTransfers: [] - }, - isLoading: false, - })), - useSaveNotionalTransfer: vi.fn(() => ({ - mutateAsync: vi.fn(), // Properly mock mutateAsync - })), - })); - -describe('AddEditNotionalTransfers', () => { - beforeEach(() => { - vi.resetAllMocks(); - - vi.spyOn(useNotionalTransfer, 'useSaveNotionalTransfer').mockReturnValue({ - mutateAsync: vi.fn(), // Ensure mutateAsync is mocked - }); -}); - it('renders the component successfully', async () => { - render(, { wrapper }); - - await waitFor(() => { - expect( - screen.getByText(/Add new notional transfer(s)/i) - ).toBeInTheDocument(); - }); - }); - - it('shows an error for 0 quantity', async () => { - render(, { wrapper }); - - const quantityInput = screen.getByLabelText(/quantity/i); - fireEvent.change(quantityInput, { target: { value: '0' } }); - fireEvent.blur(quantityInput); - - await waitFor(() => { - expect(screen.getByText(/quantity must be greater than 0./i)).toBeInTheDocument(); - }); - }); - - it('shows an error for empty quantity', async () => { - render(, { wrapper }); - - const quantityInput = screen.getByLabelText(/quantity/i); - fireEvent.change(quantityInput, { target: { value: '' } }); - fireEvent.blur(quantityInput); - - await waitFor(() => { - expect(screen.getByText(/quantity must be greater than 0./i)).toBeInTheDocument(); - }); - }); - - it('does not show an error for a valid quantity', async () => { - render(, { wrapper }); - - const quantityInput = screen.getByLabelText(/quantity/i); - fireEvent.change(quantityInput, { target: { value: '10' } }); - fireEvent.blur(quantityInput); - - await waitFor(() => { - expect(screen.queryByText(/quantity must be greater than 0./i)).not.toBeInTheDocument(); - }); - }); -}); From a14d1ec8da25bc5292a40ee2abdcdc8cf33d3e43 Mon Sep 17 00:00:00 2001 From: Arturo Reyes Lopez Date: Fri, 13 Dec 2024 14:18:37 -0700 Subject: [PATCH 6/6] Add validation in quantity for different schedules. --- backend/lcfs/web/api/fuel_supply/schema.py | 4 +- .../lcfs/web/api/notional_transfer/schema.py | 4 +- backend/lcfs/web/api/other_uses/schema.py | 4 +- backend/lcfs/web/api/transaction/schema.py | 4 +- backend/lcfs/web/api/transfer/schema.py | 4 +- .../AddEditAllocationAgreements.jsx | 53 ++++++++++--------- .../FuelSupplies/AddEditFuelSupplies.jsx | 39 ++++++++------ frontend/src/views/FuelSupplies/_schema.jsx | 2 - .../AddEditNotionalTransfers.jsx | 39 ++++++++------ .../src/views/OtherUses/AddEditOtherUses.jsx | 38 +++++++------ 10 files changed, 98 insertions(+), 93 deletions(-) diff --git a/backend/lcfs/web/api/fuel_supply/schema.py b/backend/lcfs/web/api/fuel_supply/schema.py index 68300b1e0..60592dffe 100644 --- a/backend/lcfs/web/api/fuel_supply/schema.py +++ b/backend/lcfs/web/api/fuel_supply/schema.py @@ -119,9 +119,7 @@ class FuelSupplyCreateUpdateSchema(BaseSchema): fuel_category_id: int end_use_id: Optional[int] = None provision_of_the_act_id: int - quantity: int = Field( - ..., gt=0, description="Quantity supplied must be greater than 0" - ) + quantity: int units: str fuel_type_other: Optional[str] = None fuel_code_id: Optional[int] = None diff --git a/backend/lcfs/web/api/notional_transfer/schema.py b/backend/lcfs/web/api/notional_transfer/schema.py index 6ca7085ea..5f6571e57 100644 --- a/backend/lcfs/web/api/notional_transfer/schema.py +++ b/backend/lcfs/web/api/notional_transfer/schema.py @@ -20,9 +20,7 @@ class NotionalTransferCreateSchema(BaseSchema): address_for_service: str fuel_category: str received_or_transferred: ReceivedOrTransferredEnumSchema - quantity: int = Field( - ..., gt=0, description="Quantity supplied must be greater than 0" - ) + quantity: int notional_transfer_id: Optional[int] = None compliance_report_id: int deleted: Optional[bool] = None diff --git a/backend/lcfs/web/api/other_uses/schema.py b/backend/lcfs/web/api/other_uses/schema.py index 03612276a..db3e591be 100644 --- a/backend/lcfs/web/api/other_uses/schema.py +++ b/backend/lcfs/web/api/other_uses/schema.py @@ -90,9 +90,7 @@ class OtherUsesCreateSchema(BaseSchema): fuel_type: str fuel_category: str provision_of_the_act: str - quantity_supplied: int = Field( - ..., gt=0, description="Quantity supplied must be greater than 0" - ) + quantity_supplied: int units: str expected_use: str fuel_code: Optional[str] = None diff --git a/backend/lcfs/web/api/transaction/schema.py b/backend/lcfs/web/api/transaction/schema.py index 8bd05856d..ad0d8411e 100644 --- a/backend/lcfs/web/api/transaction/schema.py +++ b/backend/lcfs/web/api/transaction/schema.py @@ -71,9 +71,7 @@ class TransactionViewSchema(BaseSchema): transaction_type: str from_organization: Optional[str] = None to_organization: str - quantity: int = Field( - ..., gt=0, description="Quantity supplied must be greater than 0" - ) + quantity: int price_per_unit: Optional[float] = None status: str create_date: datetime diff --git a/backend/lcfs/web/api/transfer/schema.py b/backend/lcfs/web/api/transfer/schema.py index 4d8826c10..889437c8a 100644 --- a/backend/lcfs/web/api/transfer/schema.py +++ b/backend/lcfs/web/api/transfer/schema.py @@ -48,9 +48,7 @@ class TransferSchema(BaseSchema): from_organization: TransferOrganizationSchema to_organization: TransferOrganizationSchema agreement_date: date - quantity: int = Field( - ..., gt=0, description="Quantity supplied must be greater than 0" - ) + quantity: int price_per_unit: float comments: Optional[List[TransferCommentSchema]] = None from_org_comment: Optional[str] = None diff --git a/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx b/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx index 843d41a7c..6a79fd45e 100644 --- a/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx +++ b/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx @@ -67,27 +67,22 @@ export const AddEditAllocationAgreements = () => { } }, [location.state?.message, location.state?.severity]) - const validateField = ( - params, - field, - validationFn, - errorMessage, - alertRef - ) => { - const newValue = params.newValue - - if (params.colDef.field === field) { - if (!validationFn(newValue)) { - alertRef.current?.triggerAlert({ - message: errorMessage, - severity: 'error' - }) - return false - } + const validate = (params, validationFn, errorMessage, alertRef, field = null) => { + const value = field ? params.node?.data[field] : params; + + if (field && params.colDef.field !== field) { + return true; } - return true // Proceed with the update - } + if (!validationFn(value)) { + alertRef.current?.triggerAlert({ + message: errorMessage, + severity: 'error', + }); + return false; + } + return true; // Proceed with the update + }; const onGridReady = useCallback( async (params) => { @@ -176,17 +171,23 @@ export const AddEditAllocationAgreements = () => { const onCellEditingStopped = useCallback( async (params) => { - const isValid = validateField( + if (params.oldValue === params.newValue) return + + const isValid = validate( params, + (value) => { + return value !== null && !isNaN(value) && value > 0; + }, + 'Quantity supplied must be greater than 0.', + alertRef, 'quantity', - (value) => value !== null && !isNaN(value) && value > 0, - 'Quantity must be greater than 0.', - alertRef - ) + ); - if (!isValid) return + if (!isValid) { + return + } - if (params.oldValue === params.newValue) return + if (!isValid) return params.node.updateData({ ...params.node.data, diff --git a/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx b/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx index f78eb2ab3..4badf3167 100644 --- a/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx +++ b/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx @@ -66,19 +66,20 @@ export const AddEditFuelSupplies = () => { } }, [location?.state?.message, location?.state?.severity]); - const validateField = (params, field, validationFn, errorMessage, alertRef) => { - const newValue = params.newValue; + const validate = (params, validationFn, errorMessage, alertRef, field = null) => { + const value = field ? params.node?.data[field] : params; - if (params.colDef.field === field) { - if (!validationFn(newValue)) { - alertRef.current?.triggerAlert({ - message: errorMessage, - severity: 'error', - }); - return false; - } + if (field && params.colDef.field !== field) { + return true; } + if (!validationFn(value)) { + alertRef.current?.triggerAlert({ + message: errorMessage, + severity: 'error', + }); + return false; + } return true; // Proceed with the update }; @@ -166,17 +167,21 @@ export const AddEditFuelSupplies = () => { const onCellEditingStopped = useCallback( async (params) => { - const isValid = validateField( + if (params.oldValue === params.newValue) return + + const isValid = validate( params, - 'quantity', - (value) => value !== null && !isNaN(value) && value > 0, + (value) => { + return value !== null && !isNaN(value) && value > 0; + }, 'Quantity supplied must be greater than 0.', - alertRef + alertRef, + 'quantity', ); - if (!isValid) return; - - if (params.oldValue === params.newValue) return + if (!isValid) { + return + } params.node.updateData({ ...params.node.data, diff --git a/frontend/src/views/FuelSupplies/_schema.jsx b/frontend/src/views/FuelSupplies/_schema.jsx index 83848a97c..5939d074b 100644 --- a/frontend/src/views/FuelSupplies/_schema.jsx +++ b/frontend/src/views/FuelSupplies/_schema.jsx @@ -104,7 +104,6 @@ export const fuelSupplyColDefs = (optionsData, errors, warnings) => [ params.data.provisionOfTheAct = null params.data.fuelCode = null params.data.fuelCodeId = null - params.data.quantity = 0 params.data.units = fuelType?.unit params.data.unrecognized = fuelType?.unrecognized } @@ -178,7 +177,6 @@ export const fuelSupplyColDefs = (optionsData, errors, warnings) => [ params.data.eer = null params.data.provisionOfTheAct = null params.data.fuelCode = null - params.data.quantity = 0 } return true }, diff --git a/frontend/src/views/NotionalTransfers/AddEditNotionalTransfers.jsx b/frontend/src/views/NotionalTransfers/AddEditNotionalTransfers.jsx index ad5aaee2b..5d8073495 100644 --- a/frontend/src/views/NotionalTransfers/AddEditNotionalTransfers.jsx +++ b/frontend/src/views/NotionalTransfers/AddEditNotionalTransfers.jsx @@ -47,19 +47,20 @@ export const AddEditNotionalTransfers = () => { } }, [location?.state?.message, location?.state?.severity]); - const validateField = (params, field, validationFn, errorMessage, alertRef) => { - const newValue = params.newValue; + const validate = (params, validationFn, errorMessage, alertRef, field = null) => { + const value = field ? params.node?.data[field] : params; - if (params.colDef.field === field) { - if (!validationFn(newValue)) { - alertRef.current?.triggerAlert({ - message: errorMessage, - severity: 'error', - }); - return false; - } + if (field && params.colDef.field !== field) { + return true; } + if (!validationFn(value)) { + alertRef.current?.triggerAlert({ + message: errorMessage, + severity: 'error', + }); + return false; + } return true; // Proceed with the update }; @@ -112,17 +113,21 @@ export const AddEditNotionalTransfers = () => { const onCellEditingStopped = useCallback( async (params) => { - const isValid = validateField( + if (params.oldValue === params.newValue) return + + const isValid = validate( params, + (value) => { + return value !== null && !isNaN(value) && value > 0; + }, + 'Quantity supplied must be greater than 0.', + alertRef, 'quantity', - (value) => value !== null && !isNaN(value) && value > 0, - 'Quantity must be greater than 0.', - alertRef ); - if (!isValid) return; - - if (params.oldValue === params.newValue) return + if (!isValid) { + return + } // Initialize updated data with 'pending' status params.node.updateData({ diff --git a/frontend/src/views/OtherUses/AddEditOtherUses.jsx b/frontend/src/views/OtherUses/AddEditOtherUses.jsx index f983bd120..d3fc18ec8 100644 --- a/frontend/src/views/OtherUses/AddEditOtherUses.jsx +++ b/frontend/src/views/OtherUses/AddEditOtherUses.jsx @@ -81,19 +81,20 @@ export const AddEditOtherUses = () => { return ciOfFuel }, []) - const validateField = (params, field, validationFn, errorMessage, alertRef) => { - const newValue = params.newValue; + const validate = (params, validationFn, errorMessage, alertRef, field = null) => { + const value = field ? params.node?.data[field] : params; - if (params.colDef.field === field) { - if (!validationFn(newValue)) { - alertRef.current?.triggerAlert({ - message: errorMessage, - severity: 'error', - }); - return false; - } + if (field && params.colDef.field !== field) { + return true; } + if (!validationFn(value)) { + alertRef.current?.triggerAlert({ + message: errorMessage, + severity: 'error', + }); + return false; + } return true; // Proceed with the update }; @@ -216,17 +217,22 @@ export const AddEditOtherUses = () => { const onCellEditingStopped = useCallback( async (params) => { - const isValid = validateField( + if (params.oldValue === params.newValue) return + + const isValid = validate( params, - 'quantitySupplied', - (value) => value !== null && !isNaN(value) && value > 0, + (value) => { + return value !== null && !isNaN(value) && value > 0; + }, 'Quantity supplied must be greater than 0.', - alertRef + alertRef, + 'quantitySupplied', ); - if (!isValid) return; + if (!isValid) { + return + } - if (params.oldValue === params.newValue) return params.data.complianceReportId = complianceReportId params.data.validationStatus = 'pending'