Skip to content
Open
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
Expand Up @@ -8,6 +8,7 @@ import { DateTime } from 'luxon';
import { SnackbarProvider } from 'notistack';
import TestRouter from '__tests__/util/TestRouter';
import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
import { GetUserQuery } from 'src/components/User/GetUser.generated';
import theme from 'src/theme';
import { StaffSavingFundProvider } from '../../StaffSavingFund/StaffSavingFundContext';
import {
Expand Down Expand Up @@ -65,21 +66,29 @@ const transferDefaultData: TransferModalData['transfer'] = {
interface ComponentsProps {
transfer?: TransferModalData['transfer'];
type?: TransferTypeEnum;
lastName?: string;
}

const Components = ({
transfer = transferDefaultData,
type,
lastName = 'Doe',
}: ComponentsProps) => (
<SnackbarProvider>
<ThemeProvider theme={theme}>
<LocalizationProvider dateAdapter={AdapterLuxon}>
<TestRouter router={router}>
<GqlMockedProvider<{
GetUser: GetUserQuery;
createRecurringTransfer: CreateRecurringTransferMutation;
createTransfer: CreateTransferMutation;
updateRecurringTransfer: UpdateRecurringTransferMutation;
}>
mocks={{
GetUser: {
user: { lastName },
},
}}
onCall={mutationSpy}
>
<StaffSavingFundProvider>
Expand All @@ -99,9 +108,15 @@ const Components = ({
</SnackbarProvider>
);

const renderModal = async (props: ComponentsProps = {}) => {
const result = render(<Components {...props} />);
await result.findByText(/Fund Transfer/);
return result;
};
Comment on lines +111 to +115
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

With now we're using useMemo, I'm unsure if you need this.


describe('TransferModal', () => {
it('should render the modal with correct inputs', () => {
const { getByRole, getByText } = render(<Components />);
it('should render the modal with correct inputs', async () => {
const { getByRole, getByText } = await renderModal();

expect(getByText('New Fund Transfer')).toBeInTheDocument();
expect(
Expand All @@ -115,8 +130,8 @@ describe('TransferModal', () => {
expect(getByRole('spinbutton', { name: /amount/i })).toBeInTheDocument();
});

it('should close modal when cancel button is clicked', () => {
const { getByRole } = render(<Components />);
it('should close modal when cancel button is clicked', async () => {
const { getByRole } = await renderModal();

userEvent.click(getByRole('button', { name: /cancel/i }));

Expand All @@ -125,7 +140,7 @@ describe('TransferModal', () => {

describe('Handle submit and validation', () => {
it('should show validation errors for required fields', async () => {
const { getByRole, findByText } = render(<Components />);
const { getByRole, findByText } = await renderModal();

const toAccount = getByRole('combobox', { name: /to account/i });
const amountField = getByRole('spinbutton', { name: /amount/i });
Expand All @@ -144,7 +159,7 @@ describe('TransferModal', () => {
});

it('should validate amount is greater than $0.01', async () => {
const { getByRole, findByText } = render(<Components />);
const { getByRole, findByText } = await renderModal();

const amountField = getByRole('spinbutton', { name: /amount/i });

Expand All @@ -167,10 +182,45 @@ describe('TransferModal', () => {
).toBeInTheDocument();
});

it.each([
{ label: 'empty', input: '' },
{ label: 'whitespace-only', input: ' ' },
])(
'should reject $label notes on one-time transfers',
async ({ input }) => {
const { getByRole, findByText } = await renderModal();

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(
<Components />,
);
const { getByRole, findByLabelText, getByLabelText, findByText } =
await renderModal();

userEvent.click(getByRole('radio', { name: /monthly/i }));
expect(getByRole('radio', { name: /monthly/i })).toBeChecked();
Expand All @@ -197,11 +247,12 @@ describe('TransferModal', () => {
});

it('should submit form with valid data', async () => {
const { getByRole } = render(<Components />);
const { getByRole } = await renderModal();

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 }));
Expand All @@ -212,6 +263,8 @@ describe('TransferModal', () => {
userEvent.clear(amountField);
userEvent.type(amountField, '100');

userEvent.type(noteField, 'Test note');

userEvent.click(getByRole('button', { name: /submit/i }));

await waitFor(() => {
Expand All @@ -225,11 +278,12 @@ describe('TransferModal', () => {
});

it('should handle form submission successfully', async () => {
const { getByRole } = render(<Components />);
const { getByRole } = await renderModal();

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);
Expand All @@ -241,6 +295,8 @@ describe('TransferModal', () => {
userEvent.clear(amountField);
userEvent.type(amountField, '100');

userEvent.type(noteField, 'Test note');

userEvent.click(submitButton);

await waitFor(() => {
Expand All @@ -255,7 +311,35 @@ describe('TransferModal', () => {
});

describe('Inputs', () => {
it('should populate initial values from data prop', () => {
it('should default the note to "<lastName> 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', 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', 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', async () => {
const { getByRole } = await renderModal({ lastName: ' ' });

const noteField = getByRole('textbox', { name: /note/i });
expect(noteField).toHaveValue('');
});

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',
Expand All @@ -266,16 +350,17 @@ describe('TransferModal', () => {
note: 'Test note',
};

const { getByDisplayValue, getByLabelText } = render(
<Components 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('');
});

it('should validate that from and to accounts are different', async () => {
const { getByRole, queryByRole, getAllByRole } = render(<Components />);
const { getByRole, queryByRole, getAllByRole } = await renderModal();

const [fromAccount, toAccount] = getAllByRole('combobox');

Expand All @@ -297,7 +382,7 @@ describe('TransferModal', () => {
});

it('should swap accounts when swap button is clicked', async () => {
const { getByRole, getByTestId } = render(<Components />);
const { getByRole, getByTestId } = await renderModal();

const icon = getByTestId('SwapHorizIcon');
const swapButton = icon.closest('button');
Expand Down Expand Up @@ -326,7 +411,7 @@ describe('TransferModal', () => {
});

it('should show/hide end date based on schedule selection', async () => {
const { getByRole, queryByRole, getByLabelText } = render(<Components />);
const { getByRole, queryByRole, getByLabelText } = await renderModal();

expect(
queryByRole('textbox', { name: /end date/i }),
Expand All @@ -348,7 +433,7 @@ describe('TransferModal', () => {
});

it('should show error message when monthly schedule is selected', async () => {
const { getByRole, getByLabelText, findByText } = render(<Components />);
const { getByRole, getByLabelText, findByText } = await renderModal();

userEvent.click(getByRole('radio', { name: /monthly/i }));
expect(getByRole('radio', { name: /monthly/i })).toBeChecked();
Expand All @@ -374,9 +459,10 @@ describe('TransferModal', () => {
note: 'Test note',
};

const { getByLabelText, findByText } = render(
<Components transfer={dataWithValues} type={TransferTypeEnum.Edit} />,
);
const { getByLabelText, findByText } = await renderModal({
transfer: dataWithValues,
type: TransferTypeEnum.Edit,
});

const transferDate = getByLabelText(/transfer date/i);

Expand All @@ -391,7 +477,7 @@ describe('TransferModal', () => {
});

it('should show information box when amount exceeds limit', async () => {
const { getByRole, findByRole } = render(<Components />);
const { getByRole, findByRole } = await renderModal();

const fromAccount = getByRole('combobox', { name: /from account/i });
const toAccount = getByRole('combobox', { name: /to account/i });
Expand All @@ -417,14 +503,14 @@ describe('TransferModal', () => {
).toBeInTheDocument();
});

it('should show proper currency symbol in amount field', () => {
const { getByText } = render(<Components />);
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(<Components />);
const { getByRole } = await renderModal();

expect(getByRole('radio', { name: /one time/i })).toBeChecked();

Expand All @@ -437,9 +523,10 @@ describe('TransferModal', () => {

describe('Mutations', () => {
it('should create a one-time transfer', async () => {
const { getByRole } = render(<Components />);
const { getByRole } = await renderModal();

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 }));
Expand All @@ -450,6 +537,9 @@ describe('TransferModal', () => {
userEvent.clear(amountField);
userEvent.type(amountField, '100');

userEvent.clear(noteField);
userEvent.type(noteField, 'Test note');

userEvent.click(getByRole('button', { name: /submit/i }));

await waitFor(() => {
Expand All @@ -461,7 +551,7 @@ describe('TransferModal', () => {
amount: 100,
sourceFundTypeName: 'Staff Account',
destinationFundTypeName: 'Staff Savings',
description: '',
description: 'Test note',
}),
}),
}),
Expand All @@ -470,7 +560,7 @@ describe('TransferModal', () => {
});

it('should create a recurring transfer', async () => {
const { getByRole, getByLabelText } = render(<Components />);
const { getByRole, getByLabelText } = await renderModal();

const amountField = getByRole('spinbutton', { name: /amount/i });

Expand Down Expand Up @@ -532,9 +622,10 @@ describe('TransferModal', () => {
recurringId: 'recurring-id',
};

const { getByRole, getByLabelText } = render(
<Components transfer={dataWithValues} type={TransferTypeEnum.Edit} />,
);
const { getByRole, getByLabelText } = await renderModal({
transfer: dataWithValues,
type: TransferTypeEnum.Edit,
});

const amountField = getByRole('spinbutton', { name: /amount/i });

Expand Down Expand Up @@ -585,8 +676,8 @@ describe('TransferModal', () => {
});

describe('New Mode', () => {
it('should display selects', () => {
const { getByRole } = render(<Components type={TransferTypeEnum.New} />);
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 });
Expand Down
Loading
Loading