Timeline: curator event create/edit forms (#781) #832

Open
marcel wants to merge 23 commits from feat/issue-781-timeline-curator-forms into main
3 changed files with 22 additions and 12 deletions
Showing only changes of commit 94d7d8099f - Show all commits

View File

@@ -5,10 +5,14 @@ import { getLocale } from '$lib/paraglide/runtime.js';
type DocumentListItem = components['schemas']['DocumentListItem'];
export type DocumentOption = Pick<
DocumentListItem,
'id' | 'title' | 'documentDate' | 'metaDatePrecision' | 'metaDateEnd'
>;
/**
* Chip/dedup contract for document pickers. `metaDatePrecision`/`metaDateEnd`
* are optional: the typeahead always populates them, but a TimelineEvent's
* DocumentRef (#781) carries only id/title/documentDate — formatDocumentOption
* degrades gracefully (bare title or plain date) when precision is absent.
*/
export type DocumentOption = Pick<DocumentListItem, 'id' | 'title' | 'documentDate'> &
Partial<Pick<DocumentListItem, 'metaDatePrecision' | 'metaDateEnd'>>;
export function createDocumentTypeahead() {
return createTypeahead<DocumentOption>({

View File

@@ -10,6 +10,9 @@ import { load, actions } from './+page.server';
const mockFetch = vi.fn() as unknown as typeof fetch;
// fail() returns a union type that TS won't narrow; read its data loosely.
const failData = (r: unknown) => (r as { data: Record<string, unknown> }).data;
beforeEach(() => vi.clearAllMocks());
function localsWith(perms: string[] | null) {
@@ -125,7 +128,7 @@ describe('zeitstrahl/events/[id]/edit save action (REQ-005/013)', () => {
actionEvent('save', { title: 'Umzug', type: 'PERSONAL', eventDate: '1925-04-01' })
);
expect(result).toMatchObject({ status: 409 });
expect(result.data.error).toBeTruthy();
expect(failData(result).error).toBeTruthy();
});
});
@@ -150,6 +153,6 @@ describe('zeitstrahl/events/[id]/edit delete action (REQ-006/007)', () => {
const result = await actions.delete(actionEvent('delete', {}));
expect(result).toMatchObject({ status: 500 });
expect(result.data.error).toBeTruthy();
expect(failData(result).error).toBeTruthy();
});
});

View File

@@ -10,6 +10,9 @@ import { load, actions } from './+page.server';
const mockFetch = vi.fn() as unknown as typeof fetch;
// fail() returns a union type that TS won't narrow; read its data loosely.
const failData = (r: unknown) => (r as { data: Record<string, unknown> }).data;
beforeEach(() => vi.clearAllMocks());
function localsWith(perms: string[] | null) {
@@ -142,16 +145,16 @@ describe('zeitstrahl/events/new save action (REQ-004/009/010/015)', () => {
expect(post).not.toHaveBeenCalled();
expect(result).toMatchObject({ status: 400 });
expect(result.data.personIds).toEqual(['p1', 'p2']);
expect(result.data.documentIds).toEqual(['d1']);
expect(result.data.titleError).toBeTruthy();
expect(failData(result).personIds).toEqual(['p1', 'p2']);
expect(failData(result).documentIds).toEqual(['d1']);
expect(failData(result).titleError).toBeTruthy();
});
it('surfaces both title and date errors when both blank (REQ-011)', async () => {
vi.mocked(createApiClient).mockReturnValue({ POST: vi.fn() } as never);
const result = await actions.save(saveEvent({ title: '', type: 'PERSONAL', eventDate: '' }));
expect(result.data.titleError).toBeTruthy();
expect(result.data.dateError).toBeTruthy();
expect(failData(result).titleError).toBeTruthy();
expect(failData(result).dateError).toBeTruthy();
});
it('redirects to /persons/{id} when originPersonId is a valid UUID', async () => {
@@ -201,6 +204,6 @@ describe('zeitstrahl/events/new save action (REQ-004/009/010/015)', () => {
saveEvent({ title: 'Umzug', type: 'PERSONAL', eventDate: '1925-04-01' })
);
expect(result).toMatchObject({ status: 409 });
expect(result.data.error).toBeTruthy();
expect(failData(result).error).toBeTruthy();
});
});