fix(timeline): preserve date/precision/end across a fail(400)
preservedFormFields echoed back only title/type/description/pickers, and EventForm seeded dateIso/precision/endDateIso solely from `event` (undefined on /new). So a no-JS validation-error reload silently dropped the entire When-section the curator had entered, while every other field survived. Echo eventDate/precision/eventDateEnd in the fail payload and seed the date controls from `form` ahead of `event`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,10 @@ interface FormResult {
|
|||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
|
// When-section values preserved across a fail(400) so a no-JS reload re-seeds them.
|
||||||
|
eventDate?: string;
|
||||||
|
precision?: string;
|
||||||
|
eventDateEnd?: string | null;
|
||||||
personIds?: string[];
|
personIds?: string[];
|
||||||
documentIds?: string[];
|
documentIds?: string[];
|
||||||
// Rehydrated chip data (id + label) so the pickers re-render after a fail(400)
|
// Rehydrated chip data (id + label) so the pickers re-render after a fail(400)
|
||||||
@@ -57,9 +61,11 @@ let {
|
|||||||
let title = $state(form?.title ?? event?.title ?? '');
|
let title = $state(form?.title ?? event?.title ?? '');
|
||||||
let description = $state(form?.description ?? event?.description ?? '');
|
let description = $state(form?.description ?? event?.description ?? '');
|
||||||
let type = $state<string>(form?.type ?? event?.type ?? 'PERSONAL');
|
let type = $state<string>(form?.type ?? event?.type ?? 'PERSONAL');
|
||||||
let dateIso = $state(event?.eventDate ?? '');
|
let dateIso = $state(form?.eventDate ?? event?.eventDate ?? '');
|
||||||
let precision = $state<DatePrecision>((event?.precision as DatePrecision) ?? 'DAY');
|
let precision = $state<DatePrecision>(
|
||||||
let endDateIso = $state(event?.eventDateEnd ?? '');
|
(form?.precision as DatePrecision) ?? (event?.precision as DatePrecision) ?? 'DAY'
|
||||||
|
);
|
||||||
|
let endDateIso = $state(form?.eventDateEnd ?? event?.eventDateEnd ?? '');
|
||||||
|
|
||||||
// On a fail(400) the server returns rehydrated chip data (form.persons/documents)
|
// On a fail(400) the server returns rehydrated chip data (form.persons/documents)
|
||||||
// so the pickers survive the round-trip — even without JS — ahead of the seeded
|
// so the pickers survive the round-trip — even without JS — ahead of the seeded
|
||||||
|
|||||||
@@ -132,6 +132,22 @@ describe('EventForm — delete is gated behind confirmation (REQ-006)', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('EventForm — seeds the When-section from the fail payload (review #2 — no-JS)', () => {
|
||||||
|
it('re-seeds date, precision and end-date from the form payload on /new', async () => {
|
||||||
|
renderForm({
|
||||||
|
form: { eventDate: '1944-03-12', precision: 'RANGE', eventDateEnd: '1944-03-14' }
|
||||||
|
});
|
||||||
|
|
||||||
|
// precision=RANGE seeded → end-date field revealed (proves precision survived).
|
||||||
|
await expect.element(page.getByLabelText('Enddatum')).toBeVisible();
|
||||||
|
|
||||||
|
const dateInput = document.querySelector('#eventDate') as HTMLInputElement;
|
||||||
|
expect(dateInput.value).toBe('12.03.1944');
|
||||||
|
const endInput = document.querySelector('#eventDateEnd') as HTMLInputElement;
|
||||||
|
expect(endInput.value).toBe('14.03.1944');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('EventForm — server date error wired per-field (REQ-011)', () => {
|
describe('EventForm — server date error wired per-field (REQ-011)', () => {
|
||||||
it('marks the date field aria-invalid and shows the message on a server date error', async () => {
|
it('marks the date field aria-invalid and shows the message on a server date error', async () => {
|
||||||
renderForm({ form: { dateError: 'Bitte ein Datum eingeben.' } });
|
renderForm({ form: { dateError: 'Bitte ein Datum eingeben.' } });
|
||||||
|
|||||||
@@ -90,6 +90,11 @@ export function preservedFormFields(parsed: ParsedEventForm) {
|
|||||||
title: parsed.title,
|
title: parsed.title,
|
||||||
description: parsed.description,
|
description: parsed.description,
|
||||||
type: parsed.type,
|
type: parsed.type,
|
||||||
|
// The When-section too, so a no-JS full reload re-seeds the date controls
|
||||||
|
// instead of dropping the curator's date/precision/end-date.
|
||||||
|
eventDate: parsed.eventDate,
|
||||||
|
precision: parsed.precision,
|
||||||
|
eventDateEnd: parsed.eventDateEnd,
|
||||||
personIds: parsed.personIds,
|
personIds: parsed.personIds,
|
||||||
documentIds: parsed.documentIds
|
documentIds: parsed.documentIds
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -187,6 +187,27 @@ describe('zeitstrahl/events/new save action (REQ-004/009/010/015)', () => {
|
|||||||
expect((failData(result).documents as { id: string }[])[0].id).toBe('d1');
|
expect((failData(result).documents as { id: string }[])[0].id).toBe('d1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('preserves date, precision and end-date in the fail payload (review #2 — no-JS reload)', async () => {
|
||||||
|
// A blank-title fail must echo back the entered When-section so a no-JS full
|
||||||
|
// reload re-seeds it; otherwise the curator loses the date/precision/end-date.
|
||||||
|
vi.mocked(createApiClient).mockReturnValue({ POST: vi.fn(), GET: vi.fn() } as never);
|
||||||
|
|
||||||
|
const result = await actions.save(
|
||||||
|
saveEvent({
|
||||||
|
title: '',
|
||||||
|
type: 'PERSONAL',
|
||||||
|
eventDate: '1944-03-12',
|
||||||
|
precision: 'RANGE',
|
||||||
|
eventDateEnd: '1944-03-14'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toMatchObject({ status: 400 });
|
||||||
|
expect(failData(result).eventDate).toBe('1944-03-12');
|
||||||
|
expect(failData(result).precision).toBe('RANGE');
|
||||||
|
expect(failData(result).eventDateEnd).toBe('1944-03-14');
|
||||||
|
});
|
||||||
|
|
||||||
it('surfaces both title and date errors when both blank (REQ-011)', async () => {
|
it('surfaces both title and date errors when both blank (REQ-011)', async () => {
|
||||||
vi.mocked(createApiClient).mockReturnValue({ POST: vi.fn() } as never);
|
vi.mocked(createApiClient).mockReturnValue({ POST: vi.fn() } as never);
|
||||||
const result = await actions.save(saveEvent({ title: '', type: 'PERSONAL', eventDate: '' }));
|
const result = await actions.save(saveEvent({ title: '', type: 'PERSONAL', eventDate: '' }));
|
||||||
|
|||||||
Reference in New Issue
Block a user