Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<ContextType>;
hcmMock: HcmDataQuery['hcm'];
}

const TestComponent: React.FC<TestComponentProps> = ({ contextValue }) => {
const TestComponent: React.FC<TestComponentProps> = ({ hcmMock }) => {
return (
<ThemeProvider theme={theme}>
<LocalizationProvider dateAdapter={AdapterLuxon}>
<MinisterHousingAllowanceContext.Provider
value={contextValue as ContextType}
<TestRouter>
<GqlMockedProvider<{
HcmData: HcmDataQuery;
MinistryHousingAllowanceRequests: MinistryHousingAllowanceRequestsQuery;
}>
mocks={{
HcmData: {
hcm: hcmMock,
},
MinistryHousingAllowanceRequests: {
ministryHousingAllowanceRequests: {
nodes: [],
},
},
}}
>
<IneligibleDisplay />
</MinisterHousingAllowanceContext.Provider>
</LocalizationProvider>
<MinisterHousingAllowanceProvider>
<IneligibleDisplay />
</MinisterHousingAllowanceProvider>
</GqlMockedProvider>
</TestRouter>
</ThemeProvider>
);
};

describe('IneligibleDisplay', () => {
it('should render page with single staff', () => {
const { getByText, queryByText } = render(
<TestComponent
contextValue={{
isMarried: false,
preferredName: 'John',
spousePreferredName: '',
userHcmData: {
staffInfo: {
personNumber: '000123456',
},
} as unknown as HcmData,
spouseHcmData: null,
}}
/>,
it('should render page with single ineligible staff', async () => {
const { findByRole, findByText, findByTestId } = render(
<TestComponent hcmMock={singleIneligible} />,
);

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(
<TestComponent
contextValue={{
isMarried: true,
preferredName: 'John',
spousePreferredName: 'Jane',
userHcmData: {
staffInfo: {
personNumber: '000123456',
},
} as unknown as HcmData,
spouseHcmData: {
staffInfo: {
personNumber: '100123456',
},
} as unknown as HcmData,
}}
/>,
it('should render page with married staff and ineligible spouse', async () => {
const { findByTestId } = render(
<TestComponent hcmMock={marriedSpouseIneligible} />,
);

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(
<TestComponent hcmMock={marriedUserIneligible} />,
);

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(
<TestComponent hcmMock={marriedBothIneligible} />,
);

expect(await findByTestId('user-ineligible-message')).toBeInTheDocument();
expect(queryByTestId('spouse-ineligible-message')).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,63 @@

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 (
<>
<Box mb={2}>
<Typography variant="h5">{t('Your MHA')}</Typography>
</Box>
<Box>
<Trans
i18nKey={isMarried ? 'newMhaRequestMarried' : 'newMhaRequestSingle'}
>
<p style={{ lineHeight: 1.5 }}>
Our records indicate that you have not applied for Minister&apos;s
Housing Allowance. If you would like information about applying for
one, contact Personnel Records at 407-826-2252 or{' '}
<a href="mailto:MHA@cru.org">MHA@cru.org</a>.
</p>
</Trans>
{isMarried && spouseEligibleForMHA === false && (
<Box mt={2}>
<Trans i18nKey="spouseNotEligibleMha">
<p style={{ lineHeight: 1.5 }}>
Completing a Minister&apos;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}&apos;s salary. If you
believe this is incorrect, please contact Personnel Records at
407-826-2252 or <a href="mailto:MHA@cru.org">MHA@cru.org</a>.
</p>
</Trans>
</Box>
)}
{!hcmLoading &&
isMarried &&
userEligibleForMHA &&
!spouseEligibleForMHA && (
Comment on lines +32 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the user is not eligible, but the spouse is?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked with Ryan and he said to display the same text that you would for a person who is eligible and has a spouse who is ineligible. The only difference would be flipping the names around! He is thinking that the request would be created for the person who is eligible.

<Box mt={2} data-testid="spouse-ineligible-message">
<Trans
i18nKey="spouseNotEligibleMha"
values={{ preferredName, spousePreferredName }}
>
<p style={{ lineHeight: 1.5 }}>
Completing a Minister&apos;s Housing Allowance calculation
form will submit the request for {preferredName}.{' '}
{spousePreferredName} has not completed the required IBS
courses to meet eligibility criteria.
</p>
</Trans>
</Box>
)}
{!hcmLoading &&
((isMarried && !spouseEligibleForMHA) || !userEligibleForMHA) && (
Comment on lines +50 to +51
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These checks feel like a lot in one place. Would we be able to extract these checks into a single variable instead? Even then, !hcmLoading and isMarried can possibly be it's own variable too.

const showSpouseIneligibleText = !hcmLoading && isMarried && userEligibleForMHA && !spouseEligibleForMHA

<Box mt={2} data-testid="user-ineligible-message">
<Trans i18nKey="userNotEligibleMha" values={{ preferredName }}>
<p style={{ lineHeight: 1.5 }}>
Once approved, when you calculate your salary, you will see
the approved amount that can be applied to {preferredName}
&apos;s salary. If you believe this is incorrect, please
contact Personnel Records at 407-826-2236 or{' '}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phone number in this error message (407-826-2236) differs from the phone number used in the earlier message on line 28 (407-826-2252). This inconsistency could confuse users. Verify which phone number is correct for Personnel Records and ensure it's used consistently throughout the component.

Suggested change
contact Personnel Records at 407-826-2236 or{' '}
contact Personnel Records at 407-826-2252 or{' '}

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

@zweatshirt zweatshirt Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already reached out to Ryan to find out what the correct number is. Awaiting a response.

<a href="mailto:MHA@cru.org">MHA@cru.org</a>.
</p>
</Trans>
</Box>
)}

Check warning on line 63 in src/components/Reports/MinisterHousingAllowance/MainPages/IneligibleDisplay.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ New issue: Complex Method

IneligibleDisplay:React.FC has a cyclomatic complexity of 10, threshold = 10. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
Comment on lines +50 to +63
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition for displaying the user-ineligible-message has a logic issue. The current condition ((isMarried && !spouseEligibleForMHA) || !userEligibleForMHA) will show the user-ineligible message even when the user IS eligible if they're married and the spouse is ineligible. This doesn't match the intended behavior based on the test cases and the message content.

The condition should be checking if the user is NOT eligible, or if both are married and both are ineligible. Consider simplifying to just !userEligibleForMHA since this message is specifically about the user's eligibility status and when they can see approved amounts.

Suggested change
{!hcmLoading &&
((isMarried && !spouseEligibleForMHA) || !userEligibleForMHA) && (
<Box mt={2} data-testid="user-ineligible-message">
<Trans i18nKey="userNotEligibleMha" values={{ preferredName }}>
<p style={{ lineHeight: 1.5 }}>
Once approved, when you calculate your salary, you will see
the approved amount that can be applied to {preferredName}
&apos;s salary. If you believe this is incorrect, please
contact Personnel Records at 407-826-2236 or{' '}
<a href="mailto:MHA@cru.org">MHA@cru.org</a>.
</p>
</Trans>
</Box>
)}
{!hcmLoading && !userEligibleForMHA && (
<Box mt={2} data-testid="user-ineligible-message">
<Trans i18nKey="userNotEligibleMha" values={{ preferredName }}>
<p style={{ lineHeight: 1.5 }}>
Once approved, when you calculate your salary, you will see
the approved amount that can be applied to {preferredName}
&apos;s salary. If you believe this is incorrect, please
contact Personnel Records at 407-826-2236 or{' '}
<a href="mailto:MHA@cru.org">MHA@cru.org</a>.
</p>
</Trans>
</Box>
)}

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping this as is to match the criteria of the Figma doc. Can change if needed.

</Box>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';

Check warning on line 1 in src/components/Reports/MinisterHousingAllowance/MinisterHousingAllowance.test.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ New issue: Code Duplication

The module contains 2 functions with similar structure: 'renders married, no pending, approved correctly','renders married, pending, no approved correctly'. Avoid duplicated, aka copy-pasted, code inside the module. More duplication lowers the code health.
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';
Expand All @@ -9,15 +10,21 @@
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'];
Expand All @@ -33,6 +40,7 @@
<GqlMockedProvider<{
HcmData: HcmDataQuery;
MinistryHousingAllowanceRequests: MinistryHousingAllowanceRequestsQuery;
CreateHousingAllowanceRequest: CreateHousingAllowanceRequestMutation;
}>
mocks={{
HcmData: {
Expand All @@ -44,6 +52,7 @@
},
},
}}
onCall={mutationSpy}
>
<MinisterHousingAllowanceProvider>
<MinisterHousingAllowanceReport />
Expand All @@ -66,17 +75,15 @@
expect(await findByText('John Doe')).toBeInTheDocument();
});

it('renders married, no pending, no approved correctly', async () => {
const { findByText } = render(
<TestComponent hcmMock={marriedNoMhaNoException} mhaRequestsMock={[]} />,
it('renders married with ineligible spouse, no approved requests', async () => {
const { findByText, findByTestId } = render(
<TestComponent hcmMock={marriedSpouseIneligible} mhaRequestsMock={[]} />,
);

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();
});

Expand Down Expand Up @@ -151,4 +158,48 @@

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(
<TestComponent hcmMock={singleMhaNoException} mhaRequestsMock={[]} />,
);

// 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(
<TestComponent hcmMock={singleIneligible} mhaRequestsMock={[]} />,
);

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',
);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,12 @@
spousePreferredName,
userHcmData,
spouseHcmData,
userEligibleForMHA,
spouseEligibleForMHA,
} = useMinisterHousingAllowance();

const canAccessMHA = userEligibleForMHA || spouseEligibleForMHA;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought they would have to switch screens to be a ble to see if their spouse is eligible?

What. if the primary user is not Eligible but the spouse is?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dr-bizz I asked Katelyn about this and neither of us are sure what the intended behavior there is. I assumed that the form should only apply to the user if eligible, or both if both eligible. If it's true the form can still be filled out for the spouse when the user is ineligible, I'll change that.

I'll reach out to Ryan as well to get better understanding.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to comment in IneligibleDisplay! I already heard from Ryan.


Check warning on line 47 in src/components/Reports/MinisterHousingAllowance/MinisterHousingAllowance.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ Getting worse: Complex Method

MinisterHousingAllowanceReport (top-level context) increases in cyclomatic complexity from 12 to 13, threshold = 10. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
const personNumber = userHcmData?.staffInfo?.personNumber ?? '';
const spousePersonNumber = spouseHcmData?.staffInfo?.personNumber ?? '';
const lastName = userHcmData?.staffInfo?.lastName ?? '';
Expand All @@ -56,6 +60,13 @@
const [createMHA] = useCreateHousingAllowanceRequestMutation();

const onCreateMHARequest = async () => {
if (!userEligibleForMHA) {
enqueueSnackbar(t('You are not eligible to create a new MHA request.'), {
variant: 'error',
Comment on lines +63 to +65
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I noticed in MHA is that a lot of the backend logic already had errors set up, so I was getting a double snackbar sometimes. I'm not sure if you did that in the backend, but if not, this is great!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would probably want some form of frontend validation to avoid making uneccessary requests. Maybe it's best to disable fields if they are ineligible instead, but I'm wondering what the best way to notify the user of their ineligibility might be (assuming they manage to get this far)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think with eligibility, it would be best to disable "New Request" button and maybe have an information alert box on the main page. This is what Ryan and I did with edit access and the new component I created was a safety net incase a user saved the edit page but their access changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I plan to do that, but a user can technically bypass that right? I don't know enough about MHA. I assume disabling the fields will be sufficient in this case.

});
return;
}

await createMHA({
variables: {
requestAttributes: {},
Expand Down Expand Up @@ -122,7 +133,7 @@
) : (
<>
<Stack direction="column" width={mainContentWidth}>
{hasNoRequests ? (
{hasNoRequests || !canAccessMHA ? (
<IneligibleDisplay />
Comment on lines +136 to 137
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code reads as if they have no requests they get the same message as users who are not eligilble for MHA

) : (
<EligibleDisplay isPending={isCurrentRequestPending} />
Expand All @@ -149,7 +160,7 @@
</>
)}

{previousApprovedRequest && (
{canAccessMHA && previousApprovedRequest && (
<Stack direction="column" width={mainContentWidth} mt={4}>
<CurrentBoardApproved request={previousApprovedRequest} />
</Stack>
Expand Down
Loading
Loading