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
11 changes: 2 additions & 9 deletions apps/frontend/src/app/components/ProjectFormModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,8 @@ function validate(values: ProjectFormValues): FieldErrors {

if (!values.startDate) errors.startDate = 'Please select a valid date';

// The "in progress" checkbox is what makes an end date optional, so the two
// are validated together rather than independently.
if (!values.inProgress) {
if (
!values.endDate ||
(values.startDate && values.endDate < values.startDate)
) {
errors.endDate = 'Please select a date AFTER the start date';
}
if (values.endDate && values.startDate && values.endDate < values.startDate) {
errors.endDate = 'Please select a date AFTER the start date';
}

return errors;
Expand Down
78 changes: 78 additions & 0 deletions apps/frontend/test/components/ProjectFormModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,53 @@ function renderEdit() {
);
}

function renderCreate() {
return render(
<ProjectFormModal open onClose={jest.fn()} onSaved={jest.fn()} />,
);
}

async function pickDate(label: string, day: number) {
const heading = screen.getByText(new RegExp(`^${label}`));
fireEvent.click(
within(heading.parentElement as HTMLElement).getByRole('button'),
);
const dialog = await screen.findByRole('dialog', {
name: `Choose ${label}`,
hidden: true,
});
const buttons = within(dialog).getAllByRole('button', {
name: String(day),
hidden: true,
});
const inMonth =
buttons.find((button) => !button.className.includes('opacity-50')) ??
buttons[0];
fireEvent.click(inMonth);
}

async function fillCreateForm(opts?: { startDay?: number; endDay?: number }) {
fireEvent.change(screen.getByPlaceholderText('Enter project name'), {
target: { value: 'Wells' },
});
fireEvent.change(screen.getByPlaceholderText('Enter total funding'), {
target: { value: '1000' },
});
fireEvent.change(
screen.getByPlaceholderText('Enter a short project description here'),
{ target: { value: 'Dig wells' } },
);

await pickDate('Start Date', opts?.startDay ?? 15);
if (opts?.endDay != null) {
await pickDate('End Date', opts.endDay);
}

const search = await screen.findByLabelText('Assigned Staff');
fireEvent.change(search, { target: { value: 'Ada' } });
fireEvent.click(await screen.findByRole('option', { name: 'Ada Lovelace' }));
}

beforeEach(() => {
mockFetch.mockReset();
mockFetch.mockImplementation((path: string) => {
Expand Down Expand Up @@ -181,3 +228,34 @@ describe('ProjectFormModal staff roles', () => {
expect(savedBody().members).toEqual([]);
});
});

describe('ProjectFormModal end date', () => {
it('lets you create a project without an end date', async () => {
renderCreate();
await fillCreateForm();
fireEvent.click(screen.getByRole('button', { name: 'Save' }));

await waitFor(() => {
expect(
mockFetch.mock.calls.some(([, init]) => init?.method === 'POST'),
).toBe(true);
});
expect(savedBody().end_date).toBeNull();
expect(
screen.queryByText('Please select a date AFTER the start date'),
).not.toBeInTheDocument();
});

it('still rejects an end date before the start date', async () => {
renderCreate();
await fillCreateForm({ startDay: 20, endDay: 10 });
fireEvent.click(screen.getByRole('button', { name: 'Save' }));

expect(
await screen.findByText('Please select a date AFTER the start date'),
).toBeInTheDocument();
expect(
mockFetch.mock.calls.some(([, init]) => init?.method === 'POST'),
).toBe(false);
});
});
Loading