From 9912aa451c011a8d0a41a5a615c3ec6949293493 Mon Sep 17 00:00:00 2001
From: Jeff Clay <91073628+jeff-clay@users.noreply.github.com>
Date: Mon, 11 May 2026 14:20:56 -0700
Subject: [PATCH 1/7] Require note on savings fund transfer form
---
.../TransferModal/TransferModal.test.tsx | 36 ++++++++++++++++++-
.../TransferModal/TransferModal.tsx | 18 ++++++++--
2 files changed, 51 insertions(+), 3 deletions(-)
diff --git a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
index 8449ca81cc..467bd6cc6a 100644
--- a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
+++ b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
@@ -167,6 +167,31 @@ describe('TransferModal', () => {
).toBeInTheDocument();
});
+ it('should require a note for one-time transfers', async () => {
+ const { getByRole, findByText } = render();
+
+ userEvent.click(getByRole('combobox', { name: /from account/i }));
+ userEvent.click(getByRole('option', { name: /staff account/i }));
+ userEvent.click(getByRole('combobox', { name: /to account/i }));
+ userEvent.click(getByRole('option', { name: /staff savings/i }));
+
+ const amountField = getByRole('spinbutton', { name: /amount/i });
+ userEvent.clear(amountField);
+ userEvent.type(amountField, '100');
+
+ // Note intentionally left blank.
+ userEvent.click(getByRole('button', { name: /submit/i }));
+
+ expect(await findByText('Note is required')).toBeInTheDocument();
+ expect(mutationSpy).not.toHaveBeenCalledWith(
+ expect.objectContaining({
+ operation: expect.objectContaining({
+ operationName: 'CreateTransfer',
+ }),
+ }),
+ );
+ });
+
it('should validate end date is after transfer date for recurring transfers', async () => {
const { getByRole, findByLabelText, getByLabelText, findByText } = render(
,
@@ -202,6 +227,7 @@ describe('TransferModal', () => {
const fromAccount = getByRole('combobox', { name: /from account/i });
const toAccount = getByRole('combobox', { name: /to account/i });
const amountField = getByRole('spinbutton', { name: /amount/i });
+ const noteField = getByRole('textbox', { name: /note/i });
userEvent.click(fromAccount);
userEvent.click(getByRole('option', { name: /staff account/i }));
@@ -212,6 +238,8 @@ describe('TransferModal', () => {
userEvent.clear(amountField);
userEvent.type(amountField, '100');
+ userEvent.type(noteField, 'Test note');
+
userEvent.click(getByRole('button', { name: /submit/i }));
await waitFor(() => {
@@ -230,6 +258,7 @@ describe('TransferModal', () => {
const fromAccount = getByRole('combobox', { name: /from account/i });
const toAccount = getByRole('combobox', { name: /to account/i });
const amountField = getByRole('spinbutton', { name: /amount/i });
+ const noteField = getByRole('textbox', { name: /note/i });
const submitButton = getByRole('button', { name: /submit/i });
userEvent.click(fromAccount);
@@ -241,6 +270,8 @@ describe('TransferModal', () => {
userEvent.clear(amountField);
userEvent.type(amountField, '100');
+ userEvent.type(noteField, 'Test note');
+
userEvent.click(submitButton);
await waitFor(() => {
@@ -440,6 +471,7 @@ describe('TransferModal', () => {
const { getByRole } = render();
const amountField = getByRole('spinbutton', { name: /amount/i });
+ const noteField = getByRole('textbox', { name: /note/i });
userEvent.click(getByRole('combobox', { name: /from account/i }));
userEvent.click(getByRole('option', { name: /staff account/i }));
@@ -450,6 +482,8 @@ describe('TransferModal', () => {
userEvent.clear(amountField);
userEvent.type(amountField, '100');
+ userEvent.type(noteField, 'Test note');
+
userEvent.click(getByRole('button', { name: /submit/i }));
await waitFor(() => {
@@ -461,7 +495,7 @@ describe('TransferModal', () => {
amount: 100,
sourceFundTypeName: 'Staff Account',
destinationFundTypeName: 'Staff Savings',
- description: '',
+ description: 'Test note',
}),
}),
}),
diff --git a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx
index d74adb7f92..a2a8387fe7 100644
--- a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx
+++ b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx
@@ -135,7 +135,15 @@ const transferSchema = (locale: string) =>
.number()
.required(i18n.t('Amount is required'))
.min(0.01, i18n.t('Amount must be at least $0.01')),
- note: yup.string().nullable(),
+ note: yup.string().when('schedule', {
+ is: ScheduleEnum.OneTime,
+ then: (schema) =>
+ schema
+ .trim()
+ .min(1, i18n.t('Note is required'))
+ .required(i18n.t('Note is required')),
+ otherwise: (schema) => schema.nullable(),
+ }),
});
interface TransferModalProps {
data: TransferModalData;
@@ -525,11 +533,17 @@ export const TransferModal: React.FC = ({
From edda24d26392ada8856f66631d8ce0733848604a Mon Sep 17 00:00:00 2001
From: Jeff Clay <91073628+jeff-clay@users.noreply.github.com>
Date: Tue, 12 May 2026 08:54:54 -0700
Subject: [PATCH 2/7] Default note to Savings Fund Transfer in MPDX
---
.../TransferModal/TransferModal.test.tsx | 23 ++++++++++++++++++-
.../TransferModal/TransferModal.tsx | 8 ++++++-
.../TransfersPage/TransfersPage.tsx | 3 +++
3 files changed, 32 insertions(+), 2 deletions(-)
diff --git a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
index 467bd6cc6a..c1c8337798 100644
--- a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
+++ b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
@@ -65,11 +65,13 @@ const transferDefaultData: TransferModalData['transfer'] = {
interface ComponentsProps {
transfer?: TransferModalData['transfer'];
type?: TransferTypeEnum;
+ lastName?: string;
}
const Components = ({
transfer = transferDefaultData,
type,
+ lastName = 'Doe',
}: ComponentsProps) => (
@@ -90,6 +92,7 @@ const Components = ({
}}
funds={fundsMock}
handleClose={handleClose}
+ lastName={lastName}
/>
@@ -179,7 +182,10 @@ describe('TransferModal', () => {
userEvent.clear(amountField);
userEvent.type(amountField, '100');
- // Note intentionally left blank.
+ // User clears the prefilled note before submitting.
+ const noteField = getByRole('textbox', { name: /note/i });
+ userEvent.clear(noteField);
+
userEvent.click(getByRole('button', { name: /submit/i }));
expect(await findByText('Note is required')).toBeInTheDocument();
@@ -286,6 +292,20 @@ describe('TransferModal', () => {
});
describe('Inputs', () => {
+ it('should default the note to " Savings Fund Transfer in MPDX" on new one-time transfers', () => {
+ const { getByRole } = render();
+
+ const noteField = getByRole('textbox', { name: /note/i });
+ expect(noteField).toHaveValue('Sleight Savings Fund Transfer in MPDX');
+ });
+
+ it('should not default the note when lastName is missing', () => {
+ const { getByRole } = render();
+
+ const noteField = getByRole('textbox', { name: /note/i });
+ expect(noteField).toHaveValue('');
+ });
+
it('should populate initial values from data prop', () => {
const dataWithValues: TransferModalData['transfer'] = {
transferFrom: '70056dcb-1a0f-4279-b710-928bcdff811a',
@@ -482,6 +502,7 @@ describe('TransferModal', () => {
userEvent.clear(amountField);
userEvent.type(amountField, '100');
+ userEvent.clear(noteField);
userEvent.type(noteField, 'Test note');
userEvent.click(getByRole('button', { name: /submit/i }));
diff --git a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx
index a2a8387fe7..5ea0ff4ccd 100644
--- a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx
+++ b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx
@@ -149,12 +149,14 @@ interface TransferModalProps {
data: TransferModalData;
funds: FundFieldsFragment[];
handleClose: () => void;
+ lastName?: string;
}
export const TransferModal: React.FC = ({
data,
funds,
handleClose,
+ lastName,
}) => {
const { t } = useTranslation();
const locale = useLocale();
@@ -177,6 +179,10 @@ export const TransferModal: React.FC = ({
const type = data.type || TransferTypeEnum.New;
const isNew = type === TransferTypeEnum.New;
+ const defaultNote = lastName
+ ? t('{{lastName}} Savings Fund Transfer in MPDX', { lastName })
+ : '';
+
const title =
type === TransferTypeEnum.New
? t('New Fund Transfer')
@@ -264,7 +270,7 @@ export const TransferModal: React.FC = ({
status: data.transfer.status ?? '',
transferDate: data.transfer.transferDate ?? getToday(),
endDate: data.transfer.endDate ?? null,
- note: data.transfer.note ?? '',
+ note: data.transfer.note || defaultNote,
isEditing: Boolean(data.transfer.id),
originalStart: data.transfer.transferDate ?? null,
}}
diff --git a/src/components/HrTools/SavingsFundTransfer/TransfersPage/TransfersPage.tsx b/src/components/HrTools/SavingsFundTransfer/TransfersPage/TransfersPage.tsx
index 3aaf9308af..9238641ecc 100644
--- a/src/components/HrTools/SavingsFundTransfer/TransfersPage/TransfersPage.tsx
+++ b/src/components/HrTools/SavingsFundTransfer/TransfersPage/TransfersPage.tsx
@@ -24,6 +24,7 @@ import {
MultiPageHeader,
} from 'src/components/Shared/MultiPageLayout/MultiPageHeader';
import { useStaffAccountQuery } from 'src/components/Shared/StaffAccount/StaffAccount.generated';
+import { useGetUserQuery } from 'src/components/User/GetUser.generated';
import theme from 'src/theme';
import {
StaffSavingFundContext,
@@ -84,6 +85,7 @@ export const TransfersPage: React.FC = ({ title }) => {
const { data: staffAccountData, error: staffAccountError } =
useStaffAccountQuery();
+ const { data: userData } = useGetUserQuery();
const { data: reportData, loading: reportLoading } =
useReportsSavingsFundTransferQuery();
@@ -342,6 +344,7 @@ export const TransfersPage: React.FC = ({ title }) => {
handleClose={() => setModalData(null)}
data={modalData}
funds={funds}
+ lastName={userData?.user?.lastName ?? undefined}
/>
)}
From 417b0af642d7eb5bb6cebae3add0e815b46a051d Mon Sep 17 00:00:00 2001
From: Jeff Clay <91073628+jeff-clay@users.noreply.github.com>
Date: Tue, 12 May 2026 10:17:07 -0700
Subject: [PATCH 3/7] Trim lastName and lock in whitespace-only note rejection
---
.../TransferModal/TransferModal.test.tsx | 42 +++++++++++++++++++
.../TransferModal/TransferModal.tsx | 7 +++-
2 files changed, 47 insertions(+), 2 deletions(-)
diff --git a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
index c1c8337798..6bd60d4005 100644
--- a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
+++ b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
@@ -198,6 +198,34 @@ describe('TransferModal', () => {
);
});
+ it('should reject whitespace-only notes on one-time transfers', async () => {
+ const { getByRole, findByText } = render();
+
+ userEvent.click(getByRole('combobox', { name: /from account/i }));
+ userEvent.click(getByRole('option', { name: /staff account/i }));
+ userEvent.click(getByRole('combobox', { name: /to account/i }));
+ userEvent.click(getByRole('option', { name: /staff savings/i }));
+
+ const amountField = getByRole('spinbutton', { name: /amount/i });
+ userEvent.clear(amountField);
+ userEvent.type(amountField, '100');
+
+ const noteField = getByRole('textbox', { name: /note/i });
+ userEvent.clear(noteField);
+ userEvent.type(noteField, ' ');
+
+ userEvent.click(getByRole('button', { name: /submit/i }));
+
+ expect(await findByText('Note is required')).toBeInTheDocument();
+ expect(mutationSpy).not.toHaveBeenCalledWith(
+ expect.objectContaining({
+ operation: expect.objectContaining({
+ operationName: 'CreateTransfer',
+ }),
+ }),
+ );
+ });
+
it('should validate end date is after transfer date for recurring transfers', async () => {
const { getByRole, findByLabelText, getByLabelText, findByText } = render(
,
@@ -306,6 +334,20 @@ describe('TransferModal', () => {
expect(noteField).toHaveValue('');
});
+ it('should trim lastName when building the default note', () => {
+ const { getByRole } = render();
+
+ const noteField = getByRole('textbox', { name: /note/i });
+ expect(noteField).toHaveValue('Sleight Savings Fund Transfer in MPDX');
+ });
+
+ it('should not default the note when lastName is only whitespace', () => {
+ const { getByRole } = render();
+
+ const noteField = getByRole('textbox', { name: /note/i });
+ expect(noteField).toHaveValue('');
+ });
+
it('should populate initial values from data prop', () => {
const dataWithValues: TransferModalData['transfer'] = {
transferFrom: '70056dcb-1a0f-4279-b710-928bcdff811a',
diff --git a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx
index 5ea0ff4ccd..6c1c50d407 100644
--- a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx
+++ b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx
@@ -179,8 +179,11 @@ export const TransferModal: React.FC = ({
const type = data.type || TransferTypeEnum.New;
const isNew = type === TransferTypeEnum.New;
- const defaultNote = lastName
- ? t('{{lastName}} Savings Fund Transfer in MPDX', { lastName })
+ const trimmedLastName = lastName?.trim();
+ const defaultNote = trimmedLastName
+ ? t('{{lastName}} Savings Fund Transfer in MPDX', {
+ lastName: trimmedLastName,
+ })
: '';
const title =
From b05637ed5c7d5c7c00072d835ceeb0bc410cadd7 Mon Sep 17 00:00:00 2001
From: Jeff Clay <91073628+jeff-clay@users.noreply.github.com>
Date: Tue, 12 May 2026 10:35:53 -0700
Subject: [PATCH 4/7] Consolidate empty/whitespace note tests with it.each
---
.../TransferModal/TransferModal.test.tsx | 88 +++++++------------
1 file changed, 34 insertions(+), 54 deletions(-)
diff --git a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
index 6bd60d4005..0da21fd67f 100644
--- a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
+++ b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
@@ -170,61 +170,41 @@ describe('TransferModal', () => {
).toBeInTheDocument();
});
- it('should require a note for one-time transfers', async () => {
- const { getByRole, findByText } = render();
-
- userEvent.click(getByRole('combobox', { name: /from account/i }));
- userEvent.click(getByRole('option', { name: /staff account/i }));
- userEvent.click(getByRole('combobox', { name: /to account/i }));
- userEvent.click(getByRole('option', { name: /staff savings/i }));
-
- const amountField = getByRole('spinbutton', { name: /amount/i });
- userEvent.clear(amountField);
- userEvent.type(amountField, '100');
-
- // User clears the prefilled note before submitting.
- const noteField = getByRole('textbox', { name: /note/i });
- userEvent.clear(noteField);
-
- userEvent.click(getByRole('button', { name: /submit/i }));
-
- expect(await findByText('Note is required')).toBeInTheDocument();
- expect(mutationSpy).not.toHaveBeenCalledWith(
- expect.objectContaining({
- operation: expect.objectContaining({
- operationName: 'CreateTransfer',
- }),
- }),
- );
- });
-
- it('should reject whitespace-only notes on one-time transfers', async () => {
- const { getByRole, findByText } = render();
-
- userEvent.click(getByRole('combobox', { name: /from account/i }));
- userEvent.click(getByRole('option', { name: /staff account/i }));
- userEvent.click(getByRole('combobox', { name: /to account/i }));
- userEvent.click(getByRole('option', { name: /staff savings/i }));
-
- const amountField = getByRole('spinbutton', { name: /amount/i });
- userEvent.clear(amountField);
- userEvent.type(amountField, '100');
-
- const noteField = getByRole('textbox', { name: /note/i });
- userEvent.clear(noteField);
- userEvent.type(noteField, ' ');
-
- userEvent.click(getByRole('button', { name: /submit/i }));
-
- expect(await findByText('Note is required')).toBeInTheDocument();
- expect(mutationSpy).not.toHaveBeenCalledWith(
- expect.objectContaining({
- operation: expect.objectContaining({
- operationName: 'CreateTransfer',
+ it.each([
+ { label: 'empty', input: '' },
+ { label: 'whitespace-only', input: ' ' },
+ ])(
+ 'should reject $label notes on one-time transfers',
+ async ({ input }) => {
+ const { getByRole, findByText } = render();
+
+ userEvent.click(getByRole('combobox', { name: /from account/i }));
+ userEvent.click(getByRole('option', { name: /staff account/i }));
+ userEvent.click(getByRole('combobox', { name: /to account/i }));
+ userEvent.click(getByRole('option', { name: /staff savings/i }));
+
+ const amountField = getByRole('spinbutton', { name: /amount/i });
+ userEvent.clear(amountField);
+ userEvent.type(amountField, '100');
+
+ const noteField = getByRole('textbox', { name: /note/i });
+ userEvent.clear(noteField);
+ if (input) {
+ userEvent.type(noteField, input);
+ }
+
+ userEvent.click(getByRole('button', { name: /submit/i }));
+
+ expect(await findByText('Note is required')).toBeInTheDocument();
+ expect(mutationSpy).not.toHaveBeenCalledWith(
+ expect.objectContaining({
+ operation: expect.objectContaining({
+ operationName: 'CreateTransfer',
+ }),
}),
- }),
- );
- });
+ );
+ },
+ );
it('should validate end date is after transfer date for recurring transfers', async () => {
const { getByRole, findByLabelText, getByLabelText, findByText } = render(
From 6f5c546d9e52bc69e670b348d272c11ec0a8f48b Mon Sep 17 00:00:00 2001
From: Jeff Clay <91073628+jeff-clay@users.noreply.github.com>
Date: Tue, 12 May 2026 15:41:30 -0700
Subject: [PATCH 5/7] Move user query into modal, drop redundant min
---
.../TransferModal/TransferModal.test.tsx | 29 ++++++++++++++++++-
.../TransferModal/TransferModal.tsx | 12 +++-----
.../TransfersPage/TransfersPage.tsx | 3 --
3 files changed, 32 insertions(+), 12 deletions(-)
diff --git a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
index 0da21fd67f..f7c8f048a9 100644
--- a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
+++ b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
@@ -8,6 +8,12 @@ import { DateTime } from 'luxon';
import { SnackbarProvider } from 'notistack';
import TestRouter from '__tests__/util/TestRouter';
import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
+import {
+ GetUserDocument,
+ GetUserQuery,
+} from 'src/components/User/GetUser.generated';
+import { UserTypeEnum } from 'src/graphql/types.generated';
+import { createCache } from 'src/lib/apollo/cache';
import theme from 'src/theme';
import { StaffSavingFundProvider } from '../../StaffSavingFund/StaffSavingFundContext';
import {
@@ -68,6 +74,27 @@ interface ComponentsProps {
lastName?: string;
}
+const buildCacheWithUser = (lastName: string) => {
+ const cache = createCache();
+ cache.writeQuery({
+ query: GetUserDocument,
+ data: {
+ user: {
+ __typename: 'User',
+ id: 'test-user-id',
+ firstName: '',
+ lastName,
+ avatar: '',
+ staffAccountId: null,
+ primaryDesignation: null,
+ userType: UserTypeEnum.UsStaff,
+ preferences: null,
+ },
+ },
+ });
+ return cache;
+};
+
const Components = ({
transfer = transferDefaultData,
type,
@@ -82,6 +109,7 @@ const Components = ({
createTransfer: CreateTransferMutation;
updateRecurringTransfer: UpdateRecurringTransferMutation;
}>
+ cache={buildCacheWithUser(lastName)}
onCall={mutationSpy}
>
@@ -92,7 +120,6 @@ const Components = ({
}}
funds={fundsMock}
handleClose={handleClose}
- lastName={lastName}
/>
diff --git a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx
index 6c1c50d407..607bef30c3 100644
--- a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx
+++ b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx
@@ -30,6 +30,7 @@ import {
SubmitButton,
} from 'src/components/Shared/Modal/ActionButtons/ActionButtons';
import Modal from 'src/components/Shared/Modal/Modal';
+import { useGetUserQuery } from 'src/components/User/GetUser.generated';
import { useLocale } from 'src/hooks/useLocale';
import i18n from 'src/lib/i18n';
import { currencyFormat, dateFormat } from 'src/lib/intlFormat';
@@ -137,11 +138,7 @@ const transferSchema = (locale: string) =>
.min(0.01, i18n.t('Amount must be at least $0.01')),
note: yup.string().when('schedule', {
is: ScheduleEnum.OneTime,
- then: (schema) =>
- schema
- .trim()
- .min(1, i18n.t('Note is required'))
- .required(i18n.t('Note is required')),
+ then: (schema) => schema.trim().required(i18n.t('Note is required')),
otherwise: (schema) => schema.nullable(),
}),
});
@@ -149,14 +146,12 @@ interface TransferModalProps {
data: TransferModalData;
funds: FundFieldsFragment[];
handleClose: () => void;
- lastName?: string;
}
export const TransferModal: React.FC = ({
data,
funds,
handleClose,
- lastName,
}) => {
const { t } = useTranslation();
const locale = useLocale();
@@ -179,7 +174,8 @@ export const TransferModal: React.FC = ({
const type = data.type || TransferTypeEnum.New;
const isNew = type === TransferTypeEnum.New;
- const trimmedLastName = lastName?.trim();
+ const { data: userData } = useGetUserQuery();
+ const trimmedLastName = userData?.user?.lastName?.trim();
const defaultNote = trimmedLastName
? t('{{lastName}} Savings Fund Transfer in MPDX', {
lastName: trimmedLastName,
diff --git a/src/components/HrTools/SavingsFundTransfer/TransfersPage/TransfersPage.tsx b/src/components/HrTools/SavingsFundTransfer/TransfersPage/TransfersPage.tsx
index 9238641ecc..3aaf9308af 100644
--- a/src/components/HrTools/SavingsFundTransfer/TransfersPage/TransfersPage.tsx
+++ b/src/components/HrTools/SavingsFundTransfer/TransfersPage/TransfersPage.tsx
@@ -24,7 +24,6 @@ import {
MultiPageHeader,
} from 'src/components/Shared/MultiPageLayout/MultiPageHeader';
import { useStaffAccountQuery } from 'src/components/Shared/StaffAccount/StaffAccount.generated';
-import { useGetUserQuery } from 'src/components/User/GetUser.generated';
import theme from 'src/theme';
import {
StaffSavingFundContext,
@@ -85,7 +84,6 @@ export const TransfersPage: React.FC = ({ title }) => {
const { data: staffAccountData, error: staffAccountError } =
useStaffAccountQuery();
- const { data: userData } = useGetUserQuery();
const { data: reportData, loading: reportLoading } =
useReportsSavingsFundTransferQuery();
@@ -344,7 +342,6 @@ export const TransfersPage: React.FC = ({ title }) => {
handleClose={() => setModalData(null)}
data={modalData}
funds={funds}
- lastName={userData?.user?.lastName ?? undefined}
/>
)}
From fcda99171e02ee31e4351fd20173f391ea72806c Mon Sep 17 00:00:00 2001
From: Jeff Clay <91073628+jeff-clay@users.noreply.github.com>
Date: Thu, 14 May 2026 10:01:04 -0700
Subject: [PATCH 6/7] Use mocks instead of cache; guard modal while loading
---
.../TransferModal/TransferModal.test.tsx | 118 +++++++-----------
.../TransferModal/TransferModal.tsx | 7 +-
2 files changed, 54 insertions(+), 71 deletions(-)
diff --git a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
index f7c8f048a9..aad59597c0 100644
--- a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
+++ b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
@@ -8,12 +8,7 @@ import { DateTime } from 'luxon';
import { SnackbarProvider } from 'notistack';
import TestRouter from '__tests__/util/TestRouter';
import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
-import {
- GetUserDocument,
- GetUserQuery,
-} from 'src/components/User/GetUser.generated';
-import { UserTypeEnum } from 'src/graphql/types.generated';
-import { createCache } from 'src/lib/apollo/cache';
+import { GetUserQuery } from 'src/components/User/GetUser.generated';
import theme from 'src/theme';
import { StaffSavingFundProvider } from '../../StaffSavingFund/StaffSavingFundContext';
import {
@@ -74,27 +69,6 @@ interface ComponentsProps {
lastName?: string;
}
-const buildCacheWithUser = (lastName: string) => {
- const cache = createCache();
- cache.writeQuery({
- query: GetUserDocument,
- data: {
- user: {
- __typename: 'User',
- id: 'test-user-id',
- firstName: '',
- lastName,
- avatar: '',
- staffAccountId: null,
- primaryDesignation: null,
- userType: UserTypeEnum.UsStaff,
- preferences: null,
- },
- },
- });
- return cache;
-};
-
const Components = ({
transfer = transferDefaultData,
type,
@@ -105,11 +79,16 @@ const Components = ({
- cache={buildCacheWithUser(lastName)}
+ mocks={{
+ GetUser: {
+ user: { lastName },
+ },
+ }}
onCall={mutationSpy}
>
@@ -129,9 +108,15 @@ const Components = ({
);
+const renderModal = async (props: ComponentsProps = {}) => {
+ const result = render();
+ await result.findByText(/Fund Transfer/);
+ return result;
+};
+
describe('TransferModal', () => {
- it('should render the modal with correct inputs', () => {
- const { getByRole, getByText } = render();
+ it('should render the modal with correct inputs', async () => {
+ const { getByRole, getByText } = await renderModal();
expect(getByText('New Fund Transfer')).toBeInTheDocument();
expect(
@@ -145,8 +130,8 @@ describe('TransferModal', () => {
expect(getByRole('spinbutton', { name: /amount/i })).toBeInTheDocument();
});
- it('should close modal when cancel button is clicked', () => {
- const { getByRole } = render();
+ it('should close modal when cancel button is clicked', async () => {
+ const { getByRole } = await renderModal();
userEvent.click(getByRole('button', { name: /cancel/i }));
@@ -155,7 +140,7 @@ describe('TransferModal', () => {
describe('Handle submit and validation', () => {
it('should show validation errors for required fields', async () => {
- const { getByRole, findByText } = render();
+ const { getByRole, findByText } = await renderModal();
const toAccount = getByRole('combobox', { name: /to account/i });
const amountField = getByRole('spinbutton', { name: /amount/i });
@@ -174,7 +159,7 @@ describe('TransferModal', () => {
});
it('should validate amount is greater than $0.01', async () => {
- const { getByRole, findByText } = render();
+ const { getByRole, findByText } = await renderModal();
const amountField = getByRole('spinbutton', { name: /amount/i });
@@ -203,7 +188,7 @@ describe('TransferModal', () => {
])(
'should reject $label notes on one-time transfers',
async ({ input }) => {
- const { getByRole, findByText } = render();
+ const { getByRole, findByText } = await renderModal();
userEvent.click(getByRole('combobox', { name: /from account/i }));
userEvent.click(getByRole('option', { name: /staff account/i }));
@@ -234,9 +219,8 @@ describe('TransferModal', () => {
);
it('should validate end date is after transfer date for recurring transfers', async () => {
- const { getByRole, findByLabelText, getByLabelText, findByText } = render(
- ,
- );
+ const { getByRole, findByLabelText, getByLabelText, findByText } =
+ await renderModal();
userEvent.click(getByRole('radio', { name: /monthly/i }));
expect(getByRole('radio', { name: /monthly/i })).toBeChecked();
@@ -263,7 +247,7 @@ describe('TransferModal', () => {
});
it('should submit form with valid data', async () => {
- const { getByRole } = render();
+ const { getByRole } = await renderModal();
const fromAccount = getByRole('combobox', { name: /from account/i });
const toAccount = getByRole('combobox', { name: /to account/i });
@@ -294,7 +278,7 @@ describe('TransferModal', () => {
});
it('should handle form submission successfully', async () => {
- const { getByRole } = render();
+ const { getByRole } = await renderModal();
const fromAccount = getByRole('combobox', { name: /from account/i });
const toAccount = getByRole('combobox', { name: /to account/i });
@@ -327,35 +311,35 @@ describe('TransferModal', () => {
});
describe('Inputs', () => {
- it('should default the note to " Savings Fund Transfer in MPDX" on new one-time transfers', () => {
- const { getByRole } = render();
+ it('should default the note to " Savings Fund Transfer in MPDX" on new one-time transfers', async () => {
+ const { getByRole } = await renderModal({ lastName: 'Sleight' });
const noteField = getByRole('textbox', { name: /note/i });
expect(noteField).toHaveValue('Sleight Savings Fund Transfer in MPDX');
});
- it('should not default the note when lastName is missing', () => {
- const { getByRole } = render();
+ it('should not default the note when lastName is missing', async () => {
+ const { getByRole } = await renderModal({ lastName: '' });
const noteField = getByRole('textbox', { name: /note/i });
expect(noteField).toHaveValue('');
});
- it('should trim lastName when building the default note', () => {
- const { getByRole } = render();
+ it('should trim lastName when building the default note', async () => {
+ const { getByRole } = await renderModal({ lastName: ' Sleight ' });
const noteField = getByRole('textbox', { name: /note/i });
expect(noteField).toHaveValue('Sleight Savings Fund Transfer in MPDX');
});
- it('should not default the note when lastName is only whitespace', () => {
- const { getByRole } = render();
+ it('should not default the note when lastName is only whitespace', async () => {
+ const { getByRole } = await renderModal({ lastName: ' ' });
const noteField = getByRole('textbox', { name: /note/i });
expect(noteField).toHaveValue('');
});
- it('should populate initial values from data prop', () => {
+ it('should populate initial values from data prop', async () => {
const dataWithValues: TransferModalData['transfer'] = {
transferFrom: '70056dcb-1a0f-4279-b710-928bcdff811a',
transferTo: '408caf15-cdfd-41d1-8778-aa42a6561b85',
@@ -366,16 +350,14 @@ describe('TransferModal', () => {
note: 'Test note',
};
- const { getByDisplayValue, getByLabelText } = render(
- ,
- );
+ const { getByDisplayValue, getByLabelText } = await renderModal({ transfer: dataWithValues, type: TransferTypeEnum.Edit });
expect(getByDisplayValue('500')).toBeInTheDocument();
expect(getByLabelText(/end date/i)).toHaveValue('');
});
it('should validate that from and to accounts are different', async () => {
- const { getByRole, queryByRole, getAllByRole } = render();
+ const { getByRole, queryByRole, getAllByRole } = await renderModal();
const [fromAccount, toAccount] = getAllByRole('combobox');
@@ -397,7 +379,7 @@ describe('TransferModal', () => {
});
it('should swap accounts when swap button is clicked', async () => {
- const { getByRole, getByTestId } = render();
+ const { getByRole, getByTestId } = await renderModal();
const icon = getByTestId('SwapHorizIcon');
const swapButton = icon.closest('button');
@@ -426,7 +408,7 @@ describe('TransferModal', () => {
});
it('should show/hide end date based on schedule selection', async () => {
- const { getByRole, queryByRole, getByLabelText } = render();
+ const { getByRole, queryByRole, getByLabelText } = await renderModal();
expect(
queryByRole('textbox', { name: /end date/i }),
@@ -448,7 +430,7 @@ describe('TransferModal', () => {
});
it('should show error message when monthly schedule is selected', async () => {
- const { getByRole, getByLabelText, findByText } = render();
+ const { getByRole, getByLabelText, findByText } = await renderModal();
userEvent.click(getByRole('radio', { name: /monthly/i }));
expect(getByRole('radio', { name: /monthly/i })).toBeChecked();
@@ -474,9 +456,7 @@ describe('TransferModal', () => {
note: 'Test note',
};
- const { getByLabelText, findByText } = render(
- ,
- );
+ const { getByLabelText, findByText } = await renderModal({ transfer: dataWithValues, type: TransferTypeEnum.Edit });
const transferDate = getByLabelText(/transfer date/i);
@@ -491,7 +471,7 @@ describe('TransferModal', () => {
});
it('should show information box when amount exceeds limit', async () => {
- const { getByRole, findByRole } = render();
+ const { getByRole, findByRole } = await renderModal();
const fromAccount = getByRole('combobox', { name: /from account/i });
const toAccount = getByRole('combobox', { name: /to account/i });
@@ -517,14 +497,14 @@ describe('TransferModal', () => {
).toBeInTheDocument();
});
- it('should show proper currency symbol in amount field', () => {
- const { getByText } = render();
+ it('should show proper currency symbol in amount field', async () => {
+ const { getByText } = await renderModal();
expect(getByText('$')).toBeInTheDocument();
});
it('should handle different schedule types', async () => {
- const { getByRole } = render();
+ const { getByRole } = await renderModal();
expect(getByRole('radio', { name: /one time/i })).toBeChecked();
@@ -537,7 +517,7 @@ describe('TransferModal', () => {
describe('Mutations', () => {
it('should create a one-time transfer', async () => {
- const { getByRole } = render();
+ const { getByRole } = await renderModal();
const amountField = getByRole('spinbutton', { name: /amount/i });
const noteField = getByRole('textbox', { name: /note/i });
@@ -574,7 +554,7 @@ describe('TransferModal', () => {
});
it('should create a recurring transfer', async () => {
- const { getByRole, getByLabelText } = render();
+ const { getByRole, getByLabelText } = await renderModal();
const amountField = getByRole('spinbutton', { name: /amount/i });
@@ -636,9 +616,7 @@ describe('TransferModal', () => {
recurringId: 'recurring-id',
};
- const { getByRole, getByLabelText } = render(
- ,
- );
+ const { getByRole, getByLabelText } = await renderModal({ transfer: dataWithValues, type: TransferTypeEnum.Edit });
const amountField = getByRole('spinbutton', { name: /amount/i });
@@ -689,8 +667,8 @@ describe('TransferModal', () => {
});
describe('New Mode', () => {
- it('should display selects', () => {
- const { getByRole } = render();
+ it('should display selects', async () => {
+ const { getByRole } = await renderModal({ type: TransferTypeEnum.New });
const fromAccount = getByRole('combobox', { name: /from account/i });
const toAccount = getByRole('combobox', { name: /to account/i });
diff --git a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx
index 607bef30c3..2104ee13cd 100644
--- a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx
+++ b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx
@@ -174,7 +174,12 @@ export const TransferModal: React.FC = ({
const type = data.type || TransferTypeEnum.New;
const isNew = type === TransferTypeEnum.New;
- const { data: userData } = useGetUserQuery();
+ const { data: userData, loading: userLoading } = useGetUserQuery();
+
+ if (userLoading) {
+ return null;
+ }
+
const trimmedLastName = userData?.user?.lastName?.trim();
const defaultNote = trimmedLastName
? t('{{lastName}} Savings Fund Transfer in MPDX', {
From 2ae7c7daf5f498d82de3476487198ebf47651a18 Mon Sep 17 00:00:00 2001
From: Jeff Clay <91073628+jeff-clay@users.noreply.github.com>
Date: Thu, 14 May 2026 10:17:04 -0700
Subject: [PATCH 7/7] Run prettier on test file
---
.../TransferModal/TransferModal.test.tsx | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
index aad59597c0..6f5d31751a 100644
--- a/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
+++ b/src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx
@@ -350,7 +350,10 @@ describe('TransferModal', () => {
note: 'Test note',
};
- const { getByDisplayValue, getByLabelText } = await renderModal({ transfer: dataWithValues, type: TransferTypeEnum.Edit });
+ const { getByDisplayValue, getByLabelText } = await renderModal({
+ transfer: dataWithValues,
+ type: TransferTypeEnum.Edit,
+ });
expect(getByDisplayValue('500')).toBeInTheDocument();
expect(getByLabelText(/end date/i)).toHaveValue('');
@@ -456,7 +459,10 @@ describe('TransferModal', () => {
note: 'Test note',
};
- const { getByLabelText, findByText } = await renderModal({ transfer: dataWithValues, type: TransferTypeEnum.Edit });
+ const { getByLabelText, findByText } = await renderModal({
+ transfer: dataWithValues,
+ type: TransferTypeEnum.Edit,
+ });
const transferDate = getByLabelText(/transfer date/i);
@@ -616,7 +622,10 @@ describe('TransferModal', () => {
recurringId: 'recurring-id',
};
- const { getByRole, getByLabelText } = await renderModal({ transfer: dataWithValues, type: TransferTypeEnum.Edit });
+ const { getByRole, getByLabelText } = await renderModal({
+ transfer: dataWithValues,
+ type: TransferTypeEnum.Edit,
+ });
const amountField = getByRole('spinbutton', { name: /amount/i });