Skip to content
Merged
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,4 +1,4 @@
import React from 'react';

Check notice on line 1 in src/components/HrTools/PdsGoalCalculator/PdsGoalCalculator.test.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

✅ No longer an issue: Code Duplication

The module no longer contains too many functions with similar structure
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {
Expand Down Expand Up @@ -60,7 +60,7 @@
expect(continueButton).not.toBeDisabled();
});

it('re-enables Continue when switching from Hourly to Salaried hides the only invalid field', async () => {
it('re-enables Continue after switching from Hourly to Salaried and entering a new Pay Rate', async () => {
const { findByRole, getByRole, queryByRole } = render(
<PdsGoalCalculatorTestWrapper
calculationMock={{
Expand Down Expand Up @@ -94,6 +94,11 @@
queryByRole('spinbutton', { name: 'Hours Worked' }),
).not.toBeInTheDocument();
});
// Switching Pay Type clears payRate, so the user must re-enter it before
// Continue becomes enabled.
const payRateInput = await findByRole('spinbutton', { name: 'Pay Rate' });
userEvent.type(payRateInput, '50000');

await waitFor(() => expect(continueButton).not.toBeDisabled());
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ describe('AnnualReimbursableSection', () => {
expect(getAllByRole('row')).toHaveLength(5);
});

it('renders the info tooltip icon with an accessible label', async () => {
const { findByLabelText } = render(<TestComponent />);
it('renders the description text below the heading', async () => {
const { findByText } = render(<TestComponent />);

expect(
await findByLabelText(
await findByText(
'This annual amount will be divided by 12 when added to the total.',
),
).toBeInTheDocument();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const AnnualReimbursableSection: React.FC = () => {
return (
<ReimbursableExpensesGrid
title={t('Annual Reimbursable Expenses')}
titleTooltip={t(
description={t(
'This annual amount will be divided by 12 when added to the total.',
)}
fields={fields}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {
MpdGoalMiscConstantCategoryEnum,
MpdGoalMiscConstantLabelEnum,
Expand All @@ -8,15 +9,24 @@ import { PdsGoalCalculatorTestWrapper } from '../PdsGoalCalculatorTestWrapper';
import { MonthlyReimbursableSection } from './MonthlyReimbursableSection';
import { editAmountCell } from './reimbursableExpensesTestUtils';

const prepopulatedTooltipText =
'Pre-filled with the maximum allowed amount. Edit to a lower value if needed.';

const mutationSpy = jest.fn();

const TestComponent: React.FC = () => (
interface TestComponentProps {
ministryInternet?: number;
}

const TestComponent: React.FC<TestComponentProps> = ({
ministryInternet = 30,
}) => (
<PdsGoalCalculatorTestWrapper
onCall={mutationSpy}
calculationMock={{
id: 'goal-1',
ministryCellPhone: 35,
ministryInternet: 30,
ministryInternet,
mpdNewsletter: 25,
mpdMiscellaneous: 10,
accountTransfers: 50,
Expand All @@ -42,6 +52,43 @@ const TestComponent: React.FC = () => (
);

describe('MonthlyReimbursableSection', () => {
it('renders the description text below the heading', async () => {
const { findByText } = render(<TestComponent />);

expect(
await findByText(
'Ministry Cell Phone and Ministry Internet reimbursements are capped at the per-month maximums shown next to each field name. Amounts entered above the maximum will be saved as the maximum.',
),
).toBeInTheDocument();
});

it('shows each maximum inline with the field name', async () => {
const { findByRole, getByRole } = render(<TestComponent />);

expect(
await findByRole('gridcell', {
name: /Ministry Cell Phone \(max \$35\/mo\)/,
}),
).toBeInTheDocument();
expect(
getByRole('gridcell', {
name: /Ministry Internet \(max \$30\/mo\)/,
}),
).toBeInTheDocument();
});

it('renders an info icon on the cell phone and internet rows with a prepopulation tooltip', async () => {
const { findAllByLabelText, findByRole } = render(<TestComponent />);

const icons = await findAllByLabelText(prepopulatedTooltipText);
expect(icons).toHaveLength(2);

userEvent.hover(icons[0]);
expect(await findByRole('tooltip')).toHaveTextContent(
prepopulatedTooltipText,
);
});

it('renders the section heading and column headers', async () => {
const { findByRole, getByRole } = render(<TestComponent />);

Expand All @@ -57,7 +104,9 @@ describe('MonthlyReimbursableSection', () => {
it('renders a row for every monthly field plus the subtotal', async () => {
const { findByRole, getAllByRole } = render(<TestComponent />);

await findByRole('gridcell', { name: 'Ministry Cell Phone' });
await findByRole('gridcell', {
name: /Ministry Cell Phone \(max \$35\/mo\)/,
});
// 1 header row + 6 field rows + 1 subtotal row
expect(getAllByRole('row')).toHaveLength(8);
});
Expand All @@ -82,16 +131,23 @@ describe('MonthlyReimbursableSection', () => {
);
});

it('shows an error and skips saving when an amount exceeds the configured maximum', async () => {
const { findByRole } = render(<TestComponent />);
it('clips an over-max amount to the configured maximum, saves the clipped value, and notifies the user', async () => {
const { findByRole, findByText } = render(
<TestComponent ministryInternet={15} />,
);

await editAmountCell(findByRole, 'Ministry Cell Phone', '999');
await editAmountCell(findByRole, 'Ministry Internet', '999');

expect(await findByRole('alert')).toHaveTextContent(
'Amount cannot exceed $35',
await waitFor(() =>
expect(mutationSpy).toHaveGraphqlOperation('UpdatePdsGoalCalculation', {
attributes: { id: 'goal-1', ministryInternet: 30 },
}),
);

expect(mutationSpy).not.toHaveGraphqlOperation('UpdatePdsGoalCalculation');
expect(
await findByText(
'Ministry Internet (max $30/mo) reduced to its maximum of $30.',
),
).toBeInTheDocument();
});

it('shows an error and skips saving when a negative amount is entered', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useGoalCalculatorConstants } from 'src/hooks/useGoalCalculatorConstants';
import { useLocale } from 'src/hooks/useLocale';
import { currencyFormat } from 'src/lib/intlFormat';
import { usePdsGoalCalculator } from '../Shared/PdsGoalCalculatorContext';
import { calculateReimbursableTotals } from '../calculations/reimbursableExpenses';
import {
Expand All @@ -10,6 +12,7 @@ import {

export const MonthlyReimbursableSection: React.FC = () => {
const { t } = useTranslation();
const locale = useLocale();
const { calculation } = usePdsGoalCalculator();
const { goalMiscConstants } = useGoalCalculatorConstants();
const phoneMax = goalMiscConstants.REIMBURSEMENTS_WITH_MAXIMUM?.PHONE?.fee;
Expand All @@ -20,16 +23,34 @@ export const MonthlyReimbursableSection: React.FC = () => {
? calculateReimbursableTotals(calculation).monthlySubtotal
: 0;

const formatMaxPerMonth = (max: number) => currencyFormat(max, 'USD', locale);

const prepopulatedTooltip = t(
'Pre-filled with the maximum allowed amount. Edit to a lower value if needed.',
);

const fields: ReimbursableField[] = [
{
fieldName: 'ministryCellPhone',
label: t('Ministry Cell Phone'),
label:
phoneMax !== undefined
? t('Ministry Cell Phone (max {{max}}/mo)', {
max: formatMaxPerMonth(phoneMax),
})
: t('Ministry Cell Phone'),
max: phoneMax,
tooltip: prepopulatedTooltip,
},
{
fieldName: 'ministryInternet',
label: t('Ministry Internet'),
label:
internetMax !== undefined
? t('Ministry Internet (max {{max}}/mo)', {
max: formatMaxPerMonth(internetMax),
})
: t('Ministry Internet'),
max: internetMax,
tooltip: prepopulatedTooltip,
},
{ fieldName: 'mpdNewsletter', label: t('MPD Newsletter') },
{ fieldName: 'mpdMiscellaneous', label: t('MPD Miscellaneous') },
Expand All @@ -43,6 +64,9 @@ export const MonthlyReimbursableSection: React.FC = () => {
return (
<ReimbursableExpensesGrid
title={t('Monthly Reimbursable Expenses')}
description={t(
'Ministry Cell Phone and Ministry Internet reimbursements are capped at the per-month maximums shown next to each field name. Amounts entered above the maximum will be saved as the maximum.',
)}
fields={fields}
subtotalLabel={t('Subtotal Monthly')}
subtotalValue={monthlySubtotal}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,30 @@ describe('ReimbursableExpensesGrid', () => {
expect(getAllByRole('row')).toHaveLength(4);
});

it('clears the error after a subsequent valid edit', async () => {
const { findByRole, queryByRole } = render(<TestComponent />);
it('clips an over-max amount to the field maximum, saves the clipped value, and notifies the user', async () => {
const { findByRole, findByText } = render(<TestComponent />);

await editAmountCell(findByRole, 'Ministry Cell Phone', '999');

await waitFor(() =>
expect(mutationSpy).toHaveGraphqlOperation('UpdatePdsGoalCalculation', {
attributes: { id: 'goal-1', ministryCellPhone: 35 },
}),
);
expect(
await findByText('Ministry Cell Phone reduced to its maximum of $35.'),
).toBeInTheDocument();
});

it('clears the negative-amount error after a subsequent valid edit', async () => {
const { findByRole, queryByRole } = render(<TestComponent />);

await editAmountCell(findByRole, 'MPD Newsletter', '-5');
expect(await findByRole('alert')).toHaveTextContent(
'Amount cannot exceed $35',
'Amount must be positive',
);

await editAmountCell(findByRole, 'Ministry Cell Phone', '20');
await editAmountCell(findByRole, 'MPD Newsletter', '20');

await waitFor(() => expect(queryByRole('alert')).not.toBeInTheDocument());
});
Expand Down
Loading
Loading