diff --git a/src/components/Reports/MinisterHousingAllowance/MainPages/IneligibleDisplay.test.tsx b/src/components/Reports/MinisterHousingAllowance/MainPages/IneligibleDisplay.test.tsx index b0b8b9cb5b..7ca4978177 100644 --- a/src/components/Reports/MinisterHousingAllowance/MainPages/IneligibleDisplay.test.tsx +++ b/src/components/Reports/MinisterHousingAllowance/MainPages/IneligibleDisplay.test.tsx @@ -1,86 +1,93 @@ import React from 'react'; import { ThemeProvider } from '@mui/material/styles'; -import { LocalizationProvider } from '@mui/x-date-pickers'; -import { AdapterLuxon } from '@mui/x-date-pickers/AdapterLuxon'; import { render } from '@testing-library/react'; +import TestRouter from '__tests__/util/TestRouter'; +import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import theme from 'src/theme'; +import { HcmDataQuery } from '../../Shared/HcmData/HCMData.generated'; import { - ContextType, - HcmData, - MinisterHousingAllowanceContext, -} from '../Shared/Context/MinisterHousingAllowanceContext'; + marriedBothIneligible, + marriedSpouseIneligible, + marriedUserIneligible, + singleIneligible, +} from '../../Shared/HcmData/mockData'; +import { MinistryHousingAllowanceRequestsQuery } from '../MinisterHousingAllowance.generated'; +import { MinisterHousingAllowanceProvider } from '../Shared/Context/MinisterHousingAllowanceContext'; import { IneligibleDisplay } from './IneligibleDisplay'; interface TestComponentProps { - contextValue: Partial; + hcmMock: HcmDataQuery['hcm']; } -const TestComponent: React.FC = ({ contextValue }) => { +const TestComponent: React.FC = ({ hcmMock }) => { return ( - - + + mocks={{ + HcmData: { + hcm: hcmMock, + }, + MinistryHousingAllowanceRequests: { + ministryHousingAllowanceRequests: { + nodes: [], + }, + }, + }} > - - - + + + + + ); }; describe('IneligibleDisplay', () => { - it('should render page with single staff', () => { - const { getByText, queryByText } = render( - , + it('should render page with single ineligible staff', async () => { + const { findByRole, findByText, findByTestId } = render( + , ); - expect(getByText('Your MHA')).toBeInTheDocument(); expect( - getByText( + await findByRole('heading', { name: 'Your MHA' }), + ).toBeInTheDocument(); + expect( + await findByText( /our records indicate that you have not applied for minister's housing allowance/i, ), ).toBeInTheDocument(); - expect( - queryByText(/Jane has not completed the required ibs courses/i), - ).not.toBeInTheDocument(); + expect(await findByTestId('user-ineligible-message')).toBeInTheDocument(); }); - it('should render page with married staff', () => { - const { getByText } = render( - , + it('should render page with married staff and ineligible spouse', async () => { + const { findByTestId } = render( + , ); - expect( - getByText(/Jane has not completed the required ibs courses/i), - ).toBeInTheDocument(); + expect(await findByTestId('spouse-ineligible-message')).toBeInTheDocument(); + expect(await findByTestId('user-ineligible-message')).toBeInTheDocument(); + }); + + it('should render page with married staff and ineligible user', async () => { + const { findByTestId, queryByTestId } = render( + , + ); + + expect(await findByTestId('user-ineligible-message')).toBeInTheDocument(); + expect(queryByTestId('spouse-ineligible-message')).not.toBeInTheDocument(); + }); + + it('should render page with both married staff ineligible', async () => { + const { findByTestId, queryByTestId } = render( + , + ); + + expect(await findByTestId('user-ineligible-message')).toBeInTheDocument(); + expect(queryByTestId('spouse-ineligible-message')).not.toBeInTheDocument(); }); }); diff --git a/src/components/Reports/MinisterHousingAllowance/MainPages/IneligibleDisplay.tsx b/src/components/Reports/MinisterHousingAllowance/MainPages/IneligibleDisplay.tsx index f33e8031f8..d0131ab221 100644 --- a/src/components/Reports/MinisterHousingAllowance/MainPages/IneligibleDisplay.tsx +++ b/src/components/Reports/MinisterHousingAllowance/MainPages/IneligibleDisplay.tsx @@ -4,12 +4,14 @@ import { useMinisterHousingAllowance } from '../Shared/Context/MinisterHousingAl export const IneligibleDisplay: React.FC = () => { const { t } = useTranslation(); - const { isMarried, preferredName, spousePreferredName } = - useMinisterHousingAllowance(); - - // TODO - Add spouse to API and check eligibility - // We will get this from HCM data in the future - const spouseEligibleForMHA = false; + const { + isMarried, + preferredName, + spousePreferredName, + userEligibleForMHA, + spouseEligibleForMHA, + hcmLoading, + } = useMinisterHousingAllowance(); return ( <> @@ -27,21 +29,38 @@ export const IneligibleDisplay: React.FC = () => { MHA@cru.org.

- {isMarried && spouseEligibleForMHA === false && ( - - -

- Completing a Minister's Housing Allowance will submit the - request for {preferredName}. {spousePreferredName} has not - completed the required IBS courses to meet eligibility criteria. - When you calculate your salary, you will see the approved amount - that can be applied to {preferredName}'s salary. If you - believe this is incorrect, please contact Personnel Records at - 407-826-2252 or MHA@cru.org. -

-
-
- )} + {!hcmLoading && + isMarried && + userEligibleForMHA && + !spouseEligibleForMHA && ( + + +

+ Completing a Minister's Housing Allowance calculation + form will submit the request for {preferredName}.{' '} + {spousePreferredName} has not completed the required IBS + courses to meet eligibility criteria. +

+
+
+ )} + {!hcmLoading && + ((isMarried && !spouseEligibleForMHA) || !userEligibleForMHA) && ( + + +

+ Once approved, when you calculate your salary, you will see + the approved amount that can be applied to {preferredName} + 's salary. If you believe this is incorrect, please + contact Personnel Records at 407-826-2236 or{' '} + MHA@cru.org. +

+
+
+ )} ); diff --git a/src/components/Reports/MinisterHousingAllowance/MinisterHousingAllowance.test.tsx b/src/components/Reports/MinisterHousingAllowance/MinisterHousingAllowance.test.tsx index 6f7f06cbfa..ae1d353ef2 100644 --- a/src/components/Reports/MinisterHousingAllowance/MinisterHousingAllowance.test.tsx +++ b/src/components/Reports/MinisterHousingAllowance/MinisterHousingAllowance.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { ThemeProvider } from '@mui/material/styles'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { SnackbarProvider } from 'notistack'; import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; @@ -9,15 +10,21 @@ import theme from 'src/theme'; import { HcmDataQuery } from '../Shared/HcmData/HCMData.generated'; import { marriedMhaAndNoException, - marriedNoMhaNoException, + marriedSpouseIneligible, + singleIneligible, singleMhaNoException, singleNoMhaNoException, } from '../Shared/HcmData/mockData'; import { MinisterHousingAllowanceReport } from './MinisterHousingAllowance'; -import { MinistryHousingAllowanceRequestsQuery } from './MinisterHousingAllowance.generated'; +import { + CreateHousingAllowanceRequestMutation, + MinistryHousingAllowanceRequestsQuery, +} from './MinisterHousingAllowance.generated'; import { MinisterHousingAllowanceProvider } from './Shared/Context/MinisterHousingAllowanceContext'; import { mockMHARequest } from './mockData'; +const mutationSpy = jest.fn(); + interface TestComponentProps { hcmMock: HcmDataQuery['hcm']; mhaRequestsMock: MinistryHousingAllowanceRequestsQuery['ministryHousingAllowanceRequests']['nodes']; @@ -33,6 +40,7 @@ const TestComponent: React.FC = ({ mocks={{ HcmData: { @@ -44,6 +52,7 @@ const TestComponent: React.FC = ({ }, }, }} + onCall={mutationSpy} > @@ -66,17 +75,15 @@ describe('MinisterHousingAllowanceReport', () => { expect(await findByText('John Doe')).toBeInTheDocument(); }); - it('renders married, no pending, no approved correctly', async () => { - const { findByText } = render( - , + it('renders married with ineligible spouse, no approved requests', async () => { + const { findByText, findByTestId } = render( + , ); expect( await findByText(/our records indicate that you have not applied for/i), ).toBeInTheDocument(); - expect( - await findByText(/will submit the request for john. jane has not/i), - ).toBeInTheDocument(); + expect(await findByTestId('spouse-ineligible-message')).toBeInTheDocument(); expect(await findByText('John Doe and Jane Doe')).toBeInTheDocument(); }); @@ -151,4 +158,48 @@ describe('MinisterHousingAllowanceReport', () => { expect(await findByText('Current MHA Request')).toBeInTheDocument(); }); + + describe('Create MHA Request Eligibility', () => { + it('should allow create mutation when user is eligible', async () => { + const { findByText, getByRole } = render( + , + ); + + // Wait for data to load + await findByText('John Doe'); + + const button = getByRole('button', { name: 'Request New MHA' }); + expect(button).toBeEnabled(); + + userEvent.click(button); + + await waitFor(() => + expect(mutationSpy).toHaveGraphqlOperation( + 'CreateHousingAllowanceRequest', + ), + ); + }); + + it('should block create mutation when user is not eligible', async () => { + const { getByRole, findByText } = render( + , + ); + + await findByText('John Doe'); + + const button = getByRole('button', { name: 'Request New MHA' }); + userEvent.click(button); + + // Should show error message and not trigger mutation + expect( + await findByText('You are not eligible to create a new MHA request.'), + ).toBeInTheDocument(); + + await waitFor(() => { + expect(mutationSpy).not.toHaveGraphqlOperation( + 'CreateHousingAllowanceRequest', + ); + }); + }); + }); }); diff --git a/src/components/Reports/MinisterHousingAllowance/MinisterHousingAllowance.tsx b/src/components/Reports/MinisterHousingAllowance/MinisterHousingAllowance.tsx index 9e2507e5eb..85f7ba44cf 100644 --- a/src/components/Reports/MinisterHousingAllowance/MinisterHousingAllowance.tsx +++ b/src/components/Reports/MinisterHousingAllowance/MinisterHousingAllowance.tsx @@ -39,8 +39,12 @@ export const MinisterHousingAllowanceReport = () => { spousePreferredName, userHcmData, spouseHcmData, + userEligibleForMHA, + spouseEligibleForMHA, } = useMinisterHousingAllowance(); + const canAccessMHA = userEligibleForMHA || spouseEligibleForMHA; + const personNumber = userHcmData?.staffInfo?.personNumber ?? ''; const spousePersonNumber = spouseHcmData?.staffInfo?.personNumber ?? ''; const lastName = userHcmData?.staffInfo?.lastName ?? ''; @@ -56,6 +60,13 @@ export const MinisterHousingAllowanceReport = () => { const [createMHA] = useCreateHousingAllowanceRequestMutation(); const onCreateMHARequest = async () => { + if (!userEligibleForMHA) { + enqueueSnackbar(t('You are not eligible to create a new MHA request.'), { + variant: 'error', + }); + return; + } + await createMHA({ variables: { requestAttributes: {}, @@ -122,7 +133,7 @@ export const MinisterHousingAllowanceReport = () => { ) : ( <> - {hasNoRequests ? ( + {hasNoRequests || !canAccessMHA ? ( ) : ( @@ -149,7 +160,7 @@ export const MinisterHousingAllowanceReport = () => { )} - {previousApprovedRequest && ( + {canAccessMHA && previousApprovedRequest && ( diff --git a/src/components/Reports/MinisterHousingAllowance/RequestPage/RequestPage.test.tsx b/src/components/Reports/MinisterHousingAllowance/RequestPage/RequestPage.test.tsx index 7aa61e8730..e39cfff7c6 100644 --- a/src/components/Reports/MinisterHousingAllowance/RequestPage/RequestPage.test.tsx +++ b/src/components/Reports/MinisterHousingAllowance/RequestPage/RequestPage.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { ThemeProvider } from '@mui/material/styles'; import { render, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { SnackbarProvider } from 'notistack'; import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import { MhaRentOrOwnEnum } from 'src/graphql/types.generated'; @@ -69,15 +70,17 @@ const TestComponent: React.FC = ({ return ( - - - onCall={mutationSpy} - > - {content} - - + + + + onCall={mutationSpy} + > + {content} + + + ); }; @@ -181,6 +184,7 @@ describe('RequestPage', () => { hasCalcValues: true, setHasCalcValues, updateMutation, + userEligibleForMHA: true, requestData: { id: 'request-id', requestAttributes: { @@ -306,6 +310,7 @@ describe('RequestPage', () => { setHasCalcValues, updateMutation, setIsPrint, + userEligibleForMHA: true, requestData: { id: 'request-id', requestAttributes: { diff --git a/src/components/Reports/MinisterHousingAllowance/Shared/AutoSave/AutosaveCustomTextField.test.tsx b/src/components/Reports/MinisterHousingAllowance/Shared/AutoSave/AutosaveCustomTextField.test.tsx index 9b42543a55..f9e2a77caa 100644 --- a/src/components/Reports/MinisterHousingAllowance/Shared/AutoSave/AutosaveCustomTextField.test.tsx +++ b/src/components/Reports/MinisterHousingAllowance/Shared/AutoSave/AutosaveCustomTextField.test.tsx @@ -3,6 +3,7 @@ import { ThemeProvider } from '@mui/material/styles'; import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Formik } from 'formik'; +import { SnackbarProvider } from 'notistack'; import * as yup from 'yup'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import { PageEnum } from 'src/components/Reports/Shared/CalculationReports/Shared/sharedTypes'; @@ -22,32 +23,35 @@ const defaultSchema = yup.object({ }); const TestComponent: React.FC = () => ( - - - onCall={mutationSpy} - > - + + + onCall={mutationSpy} > - - - - - - + + + + + + + + ); describe('AutosaveCustomTextField', () => { @@ -76,7 +80,7 @@ describe('AutosaveCustomTextField', () => { const { getByRole } = render(); const input = getByRole('textbox'); - await userEvent.type(input, '1500'); + userEvent.type(input, '1500'); input.blur(); await waitFor(() => diff --git a/src/components/Reports/MinisterHousingAllowance/Shared/AutoSave/useSaveField.test.tsx b/src/components/Reports/MinisterHousingAllowance/Shared/AutoSave/useSaveField.test.tsx index be6389c04e..9c00c9d042 100644 --- a/src/components/Reports/MinisterHousingAllowance/Shared/AutoSave/useSaveField.test.tsx +++ b/src/components/Reports/MinisterHousingAllowance/Shared/AutoSave/useSaveField.test.tsx @@ -1,9 +1,11 @@ import { ThemeProvider } from '@mui/material/styles'; import { renderHook, waitFor } from '@testing-library/react'; +import { SnackbarProvider } from 'notistack'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import { PageEnum } from 'src/components/Reports/Shared/CalculationReports/Shared/sharedTypes'; import theme from 'src/theme'; import { UpdateMinistryHousingAllowanceRequestMutation } from '../../MinisterHousingAllowance.generated'; +import { mockMHARequest } from '../../mockData'; import { ContextType, MinisterHousingAllowanceContext, @@ -14,43 +16,49 @@ const mutationSpy = jest.fn(); interface TestComponentProps { children: React.ReactNode; + userEligibleForMHA?: boolean; } -const TestComponent: React.FC = ({ children }) => { +const TestComponent: React.FC = ({ + children, + userEligibleForMHA = true, +}) => { return ( - - onCall={mutationSpy} - > - + + onCall={mutationSpy} > - {children} - - + + {children} + + + ); }; describe('useSaveField', () => { - it('should update ministry housing allowance request', async () => { + it('should update ministry housing allowance request when user is eligible', async () => { const { result } = renderHook( () => useSaveField({ formValues: { rentalValue: 50 }, }), { - wrapper: TestComponent, + wrapper: ({ children }) => ( + {children} + ), }, ); @@ -61,7 +69,7 @@ describe('useSaveField', () => { 'UpdateMinistryHousingAllowanceRequest', { input: { - requestId: 'request-id', + requestId: '1', requestAttributes: { rentalValue: 100, }, @@ -70,4 +78,26 @@ describe('useSaveField', () => { ), ); }); + + it('should block mutation when user is not eligible', async () => { + const { result } = renderHook( + () => + useSaveField({ + formValues: { rentalValue: 50 }, + }), + { + wrapper: ({ children }) => ( + {children} + ), + }, + ); + + result.current({ rentalValue: 100 }); + + await waitFor(() => { + expect(mutationSpy).not.toHaveGraphqlOperation( + 'UpdateMinistryHousingAllowanceRequest', + ); + }); + }); }); diff --git a/src/components/Reports/MinisterHousingAllowance/Shared/AutoSave/useSaveField.ts b/src/components/Reports/MinisterHousingAllowance/Shared/AutoSave/useSaveField.ts index 09c5377632..c9e9c2bf76 100644 --- a/src/components/Reports/MinisterHousingAllowance/Shared/AutoSave/useSaveField.ts +++ b/src/components/Reports/MinisterHousingAllowance/Shared/AutoSave/useSaveField.ts @@ -1,4 +1,6 @@ import { useCallback } from 'react'; +import { useSnackbar } from 'notistack'; +import { useTranslation } from 'react-i18next'; import { MinistryHousingAllowanceRequestAttributesInput } from 'pages/api/graphql-rest.page.generated'; import { calculateAnnualTotals } from 'src/hooks/useAnnualTotal'; import { useUpdateMinistryHousingAllowanceRequestMutation } from '../../MinisterHousingAllowance.generated'; @@ -10,12 +12,14 @@ interface UseSaveFieldOptions { } export const useSaveField = ({ formValues }: UseSaveFieldOptions) => { - const { requestData } = useMinisterHousingAllowance(); + const { requestData, userEligibleForMHA } = useMinisterHousingAllowance(); const [updateMinistryHousingAllowanceRequest] = useUpdateMinistryHousingAllowanceRequestMutation({ refetchQueries: ['MinistryHousingAllowanceRequest'], }); const values = requestData?.requestAttributes; + const { t } = useTranslation(); + const { enqueueSnackbar } = useSnackbar(); const saveField = useCallback( async ( @@ -24,6 +28,15 @@ export const useSaveField = ({ formValues }: UseSaveFieldOptions) => { if (!requestData?.id) { return; } + if (!userEligibleForMHA) { + enqueueSnackbar( + t('You are not eligible to make changes to this request.'), + { + variant: 'error', + }, + ); + return; + } const updatedValues = { ...formValues, @@ -60,7 +73,14 @@ export const useSaveField = ({ formValues }: UseSaveFieldOptions) => { }); } catch (error) {} }, - [formValues, updateMinistryHousingAllowanceRequest, requestData], + [ + formValues, + updateMinistryHousingAllowanceRequest, + requestData, + userEligibleForMHA, + enqueueSnackbar, + t, + ], ); return saveField; diff --git a/src/components/Reports/MinisterHousingAllowance/Shared/Context/MinisterHousingAllowanceContext.tsx b/src/components/Reports/MinisterHousingAllowance/Shared/Context/MinisterHousingAllowanceContext.tsx index 8fd2142579..eff298fb09 100644 --- a/src/components/Reports/MinisterHousingAllowance/Shared/Context/MinisterHousingAllowanceContext.tsx +++ b/src/components/Reports/MinisterHousingAllowance/Shared/Context/MinisterHousingAllowanceContext.tsx @@ -50,6 +50,10 @@ export type ContextType = { spouseHcmData?: HcmData | null; preferredName: string; spousePreferredName: string; + userEligibleForMHA: boolean; + spouseEligibleForMHA: boolean; + hcmLoading: boolean; + hcmError?: ApolloError; requestData?: | MinistryHousingAllowanceRequestQuery['ministryHousingAllowanceRequest'] @@ -125,7 +129,11 @@ export const MinisterHousingAllowanceProvider: React.FC = ({ })); }, [initialSteps, isComplete]); - const { data: hcmData } = useHcmDataQuery(); + const { + data: hcmData, + loading: hcmLoading, + error: hcmError, + } = useHcmDataQuery(); const [userHcmData, setUserHcmData] = useState(); const [spouseHcmData, setSpouseHcmData] = useState(null); @@ -153,6 +161,15 @@ export const MinisterHousingAllowanceProvider: React.FC = ({ [spouseHcmData], ); + const userEligibleForMHA = useMemo( + () => userHcmData?.mhaEit?.mhaEligibility ?? false, + [userHcmData], + ); + const spouseEligibleForMHA = useMemo( + () => spouseHcmData?.mhaEit?.mhaEligibility ?? false, + [spouseHcmData], + ); + const [isDrawerOpen, setIsDrawerOpen] = useState(true); const toggleDrawer = useCallback(() => { setIsDrawerOpen((prev) => !prev); @@ -202,6 +219,10 @@ export const MinisterHousingAllowanceProvider: React.FC = ({ spouseHcmData, preferredName, spousePreferredName, + userEligibleForMHA, + spouseEligibleForMHA, + hcmLoading, + hcmError, isPrint, setIsPrint, setIsComplete, @@ -228,6 +249,10 @@ export const MinisterHousingAllowanceProvider: React.FC = ({ spouseHcmData, preferredName, spousePreferredName, + userEligibleForMHA, + spouseEligibleForMHA, + hcmLoading, + hcmError, isPrint, setIsPrint, setIsComplete, diff --git a/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/CalcComponents/CostOfHome.test.tsx b/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/CalcComponents/CostOfHome.test.tsx index 6274decc61..e64493a007 100644 --- a/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/CalcComponents/CostOfHome.test.tsx +++ b/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/CalcComponents/CostOfHome.test.tsx @@ -5,6 +5,7 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { render, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Formik } from 'formik'; +import { SnackbarProvider } from 'notistack'; import * as yup from 'yup'; import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; @@ -36,25 +37,27 @@ const TestComponent: React.FC = ({ rentOrOwn, contextValue, }) => ( - - - - onCall={mutationSpy} - > - - - - - - - - - - + + + + + onCall={mutationSpy} + > + + + + + + + + + + + ); describe('CostOfHome', () => { diff --git a/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/CalcComponents/EndingSection.test.tsx b/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/CalcComponents/EndingSection.test.tsx index 70a95a6605..ab03f0a47c 100644 --- a/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/CalcComponents/EndingSection.test.tsx +++ b/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/CalcComponents/EndingSection.test.tsx @@ -4,6 +4,7 @@ import { AdapterLuxon } from '@mui/x-date-pickers/AdapterLuxon'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { render } from '@testing-library/react'; import { Formik } from 'formik'; +import { SnackbarProvider } from 'notistack'; import * as yup from 'yup'; import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; @@ -20,19 +21,21 @@ const mockSchema = { } as unknown as yup.Schema; const TestComponent: React.FC = () => ( - - - - - - - - - - - - - + + + + + + + + + + + + + + + ); describe('EndingSection', () => { diff --git a/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/CalcComponents/FairRentalValue.test.tsx b/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/CalcComponents/FairRentalValue.test.tsx index a7266717b2..96aff8f724 100644 --- a/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/CalcComponents/FairRentalValue.test.tsx +++ b/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/CalcComponents/FairRentalValue.test.tsx @@ -5,6 +5,7 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { render, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Formik } from 'formik'; +import { SnackbarProvider } from 'notistack'; import * as yup from 'yup'; import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; @@ -31,25 +32,27 @@ interface TestComponentProps { } const TestComponent: React.FC = ({ contextValue }) => ( - - - - onCall={mutationSpy} - > - - - - - - - - - - + + + + + onCall={mutationSpy} + > + + + + + + + + + + + ); describe('FairRentalValue', () => { diff --git a/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/Calculation.test.tsx b/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/Calculation.test.tsx index 694b9fed7f..fbfcacdf06 100644 --- a/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/Calculation.test.tsx +++ b/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/Calculation.test.tsx @@ -5,6 +5,7 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { render, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Formik } from 'formik'; +import { SnackbarProvider } from 'notistack'; import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import { PageEnum } from 'src/components/Reports/Shared/CalculationReports/Shared/sharedTypes'; @@ -15,6 +16,7 @@ import { ContextType, MinisterHousingAllowanceContext, } from '../../Shared/Context/MinisterHousingAllowanceContext'; +import { mockMHARequest } from '../../mockData'; import { Calculation } from './Calculation'; const submit = jest.fn(); @@ -37,27 +39,29 @@ const TestComponent: React.FC = ({ rentOrOwn = MhaRentOrOwnEnum.Own, }) => ( - - - - onCall={mutationSpy} - > - - - - - - - - + + + + + onCall={mutationSpy} + > + + + + + + + + + ); @@ -215,6 +219,7 @@ describe('Calculation', () => { setHasCalcValues, setIsPrint, updateMutation, + userEligibleForMHA: true, requestData: { id: 'request-id', requestAttributes: { @@ -318,6 +323,7 @@ describe('Calculation', () => { setHasCalcValues, setIsPrint, updateMutation, + userEligibleForMHA: true, requestData: { id: 'request-id', requestAttributes: { @@ -377,5 +383,73 @@ describe('Calculation', () => { expect(queryByRole('button', { name: /back/i })).not.toBeInTheDocument(); expect(queryByRole('checkbox')).not.toBeInTheDocument(); }); + + describe('Update Checkbox Eligibility', () => { + it('should allow update mutation when user is eligible', async () => { + const { getByRole } = render( + , + ); + + const checkbox = getByRole('checkbox', { + name: /i understand that my approved/i, + }); + + userEvent.click(checkbox); + + await waitFor(() => + expect(updateMutation).toHaveBeenCalledWith({ + variables: { + input: { + requestId: '1', + requestAttributes: { + iUnderstandMhaPolicy: true, + }, + }, + }, + }), + ); + }); + + it('should block checkbox update when user is not eligible', async () => { + const { findByRole, findByText } = render( + , + ); + + const checkbox = await findByRole('checkbox', { + name: /i understand that my approved/i, + }); + + userEvent.click(checkbox); + + // Should show error message and not trigger mutation + expect( + await findByText( + 'You are not eligible to make changes to this request.', + ), + ).toBeInTheDocument(); + + await waitFor(() => { + expect(updateMutation).not.toHaveBeenCalled(); + }); + }); + }); }); }); diff --git a/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/Calculation.tsx b/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/Calculation.tsx index 0c6f7edade..f52423e4dd 100644 --- a/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/Calculation.tsx +++ b/src/components/Reports/MinisterHousingAllowance/Steps/StepThree/Calculation.tsx @@ -15,6 +15,7 @@ import { } from '@mui/material'; import { Formik } from 'formik'; import { DateTime } from 'luxon'; +import { useSnackbar } from 'notistack'; import { Trans, useTranslation } from 'react-i18next'; import * as yup from 'yup'; import { PageEnum } from 'src/components/Reports/Shared/CalculationReports/Shared/sharedTypes'; @@ -102,6 +103,7 @@ export const Calculation: React.FC = ({ const locale = useLocale(); const { query } = useRouter(); const print = query.print === 'true'; + const { enqueueSnackbar } = useSnackbar(); const { handleNextStep, @@ -113,9 +115,20 @@ export const Calculation: React.FC = ({ requestData, updateMutation, userHcmData, + userEligibleForMHA, } = useMinisterHousingAllowance(); - const updateCheckbox = (value: boolean) => + const updateCheckbox = (value: boolean) => { + if (!userEligibleForMHA) { + enqueueSnackbar( + t('You are not eligible to make changes to this request.'), + { + variant: 'error', + }, + ); + return; + } + updateMutation({ variables: { input: { @@ -126,6 +139,7 @@ export const Calculation: React.FC = ({ }, }, }); + }; const request = requestData ? requestData.requestAttributes : null; diff --git a/src/components/Reports/MinisterHousingAllowance/Steps/StepTwo/RentOwn.test.tsx b/src/components/Reports/MinisterHousingAllowance/Steps/StepTwo/RentOwn.test.tsx index d25f979a5f..c5fb3fc939 100644 --- a/src/components/Reports/MinisterHousingAllowance/Steps/StepTwo/RentOwn.test.tsx +++ b/src/components/Reports/MinisterHousingAllowance/Steps/StepTwo/RentOwn.test.tsx @@ -3,6 +3,7 @@ import { ThemeProvider } from '@mui/material/styles'; import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Formik } from 'formik'; +import { SnackbarProvider } from 'notistack'; import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import { PageEnum } from 'src/components/Reports/Shared/CalculationReports/Shared/sharedTypes'; @@ -12,6 +13,7 @@ import { ContextType, MinisterHousingAllowanceContext, } from '../../Shared/Context/MinisterHousingAllowanceContext'; +import { mockMHARequest } from '../../mockData'; import { RentOwn } from './RentOwn'; const submit = jest.fn(); @@ -25,21 +27,23 @@ interface TestComponentProps { const TestComponent: React.FC = ({ contextValue }) => ( - - - onCall={mutationSpy} - > - - - - - - - + + + + onCall={mutationSpy} + > + + + + + + + + ); @@ -52,7 +56,8 @@ describe('RentOwn', () => { pageType: PageEnum.New, updateMutation, setHasCalcValues, - requestData: { id: 'request-id' }, + requestData: mockMHARequest, + userEligibleForMHA: true, } as unknown as ContextType } />, @@ -64,7 +69,7 @@ describe('RentOwn', () => { expect(getByText('Own')).toBeInTheDocument(); expect(await findAllByRole('radio', { checked: false })).toHaveLength(2); - await userEvent.click(getByText('Rent')); + userEvent.click(getByText('Rent')); await waitFor(() => expect(updateMutation).toHaveBeenCalledWith({ @@ -83,7 +88,7 @@ describe('RentOwn', () => { overallAmount: null, iUnderstandMhaPolicy: null, }, - requestId: 'request-id', + requestId: '1', }, }, }), @@ -98,7 +103,7 @@ describe('RentOwn', () => { contextValue={ { pageType: PageEnum.Edit, - requestData: { id: 'request-id' }, + requestData: mockMHARequest, } as unknown as ContextType } />, @@ -112,4 +117,66 @@ describe('RentOwn', () => { expect(getByRole('radio', { name: 'Rent' })).not.toBeChecked(); expect(getByRole('radio', { name: 'Own' })).not.toBeChecked(); }); + + describe('Update Request Eligibility', () => { + it('should allow update mutation when user is eligible', async () => { + const { getByText } = render( + , + ); + + await userEvent.click(getByText('Rent')); + + await waitFor(() => + expect(updateMutation).toHaveBeenCalledWith({ + variables: { + input: { + requestAttributes: { + rentOrOwn: 'RENT', + rentalValue: null, + furnitureCostsOne: null, + avgUtilityOne: null, + mortgageOrRentPayment: null, + furnitureCostsTwo: null, + repairCosts: null, + avgUtilityTwo: null, + unexpectedExpenses: null, + overallAmount: null, + iUnderstandMhaPolicy: null, + }, + requestId: '1', + }, + }, + }), + ); + }); + + it('should block update mutation when user is not eligible', async () => { + const { getByText } = render( + , + ); + + await userEvent.click(getByText('Rent')); + + // Wait a moment to ensure the mutation would have been called if it was going to be + await waitFor(() => { + expect(updateMutation).not.toHaveBeenCalled(); + }); + }); + }); }); diff --git a/src/components/Reports/MinisterHousingAllowance/Steps/StepTwo/RentOwn.tsx b/src/components/Reports/MinisterHousingAllowance/Steps/StepTwo/RentOwn.tsx index 05384a2e6c..bf8d18a20a 100644 --- a/src/components/Reports/MinisterHousingAllowance/Steps/StepTwo/RentOwn.tsx +++ b/src/components/Reports/MinisterHousingAllowance/Steps/StepTwo/RentOwn.tsx @@ -9,6 +9,7 @@ import { Typography, } from '@mui/material'; import { useFormikContext } from 'formik'; +import { useSnackbar } from 'notistack'; import { useTranslation } from 'react-i18next'; import { PageEnum } from 'src/components/Reports/Shared/CalculationReports/Shared/sharedTypes'; import { MhaRentOrOwnEnum } from 'src/graphql/types.generated'; @@ -19,6 +20,7 @@ import { useMinisterHousingAllowance } from '../../Shared/Context/MinisterHousin export const RentOwn: React.FC = () => { const { t } = useTranslation(); + const { enqueueSnackbar } = useSnackbar(); const { values, @@ -38,9 +40,20 @@ export const RentOwn: React.FC = () => { handlePreviousStep, requestData, updateMutation, + userEligibleForMHA, } = useMinisterHousingAllowance(); const updateRequest = (id: string, rentOrOwn: MhaRentOrOwnEnum) => { + if (!userEligibleForMHA) { + enqueueSnackbar( + t('You are not eligible to make changes to this request.'), + { + variant: 'error', + }, + ); + return; + } + updateMutation({ variables: { input: { diff --git a/src/components/Reports/Shared/HcmData/HCMData.graphql b/src/components/Reports/Shared/HcmData/HCMData.graphql index a2ba094540..c068fb40fc 100644 --- a/src/components/Reports/Shared/HcmData/HCMData.graphql +++ b/src/components/Reports/Shared/HcmData/HCMData.graphql @@ -29,6 +29,9 @@ query HcmData { lastUpdatedDate currentApprovedAmountForStaff } + mhaEit { + mhaEligibility + } exceptionSalaryCap { amount effectiveDate diff --git a/src/components/Reports/Shared/HcmData/mockData.ts b/src/components/Reports/Shared/HcmData/mockData.ts index 393b130ee5..f4f581177b 100644 --- a/src/components/Reports/Shared/HcmData/mockData.ts +++ b/src/components/Reports/Shared/HcmData/mockData.ts @@ -46,6 +46,9 @@ const noMhaAndNoException: HcmDataQuery['hcm'][0] = { lastUpdatedDate: null, currentApprovedAmountForStaff: null, }, + mhaEit: { + mhaEligibility: true, + }, exceptionSalaryCap: { amount: null, effectiveDate: null, @@ -106,3 +109,51 @@ export const marriedNoMhaNoException: HcmDataQuery['hcm'] = [ staffInfo: janeDoe, }, ]; +export const marriedSpouseIneligible: HcmDataQuery['hcm'] = [ + noMhaAndNoException, + { + ...noMhaAndNoException, + staffInfo: janeDoe, + mhaEit: { + mhaEligibility: false, + }, + }, +]; + +export const singleIneligible: HcmDataQuery['hcm'] = [ + { + ...noMhaAndNoException, + mhaEit: { + mhaEligibility: false, + }, + }, +]; + +export const marriedUserIneligible: HcmDataQuery['hcm'] = [ + { + ...noMhaAndNoException, + mhaEit: { + mhaEligibility: false, + }, + }, + { + ...noMhaAndNoException, + staffInfo: janeDoe, + }, +]; + +export const marriedBothIneligible: HcmDataQuery['hcm'] = [ + { + ...noMhaAndNoException, + mhaEit: { + mhaEligibility: false, + }, + }, + { + ...noMhaAndNoException, + staffInfo: janeDoe, + mhaEit: { + mhaEligibility: false, + }, + }, +];