fix(types): make DocumentOption precision fields optional; narrow spec data access
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 3m0s
CI / OCR Service Tests (pull_request) Successful in 23s
CI / Backend Unit Tests (pull_request) Successful in 4m54s
CI / fail2ban Regex (pull_request) Successful in 47s
SDD Gate / RTM Check (pull_request) Successful in 15s
SDD Gate / Contract Validate (pull_request) Successful in 22s
CI / Semgrep Security Scan (pull_request) Successful in 23s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m8s
SDD Gate / Constitution Impact (pull_request) Successful in 16s
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 3m0s
CI / OCR Service Tests (pull_request) Successful in 23s
CI / Backend Unit Tests (pull_request) Successful in 4m54s
CI / fail2ban Regex (pull_request) Successful in 47s
SDD Gate / RTM Check (pull_request) Successful in 15s
SDD Gate / Contract Validate (pull_request) Successful in 22s
CI / Semgrep Security Scan (pull_request) Successful in 23s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m8s
SDD Gate / Constitution Impact (pull_request) Successful in 16s
DocumentOption's metaDatePrecision/metaDateEnd are now optional so a TimelineEvent DocumentRef (id/title/documentDate only) maps cleanly into a picker chip — formatDocumentOption already degrades gracefully when precision is absent. The server specs read fail()'s union data via a small failData() cast that TS cannot narrow. svelte-check shows zero new errors in the #781 files. Refs #781 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -5,10 +5,14 @@ import { getLocale } from '$lib/paraglide/runtime.js';
|
|||||||
|
|
||||||
type DocumentListItem = components['schemas']['DocumentListItem'];
|
type DocumentListItem = components['schemas']['DocumentListItem'];
|
||||||
|
|
||||||
export type DocumentOption = Pick<
|
/**
|
||||||
DocumentListItem,
|
* Chip/dedup contract for document pickers. `metaDatePrecision`/`metaDateEnd`
|
||||||
'id' | 'title' | 'documentDate' | '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() {
|
export function createDocumentTypeahead() {
|
||||||
return createTypeahead<DocumentOption>({
|
return createTypeahead<DocumentOption>({
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import { load, actions } from './+page.server';
|
|||||||
|
|
||||||
const mockFetch = vi.fn() as unknown as typeof fetch;
|
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());
|
beforeEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
function localsWith(perms: string[] | null) {
|
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' })
|
actionEvent('save', { title: 'Umzug', type: 'PERSONAL', eventDate: '1925-04-01' })
|
||||||
);
|
);
|
||||||
expect(result).toMatchObject({ status: 409 });
|
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', {}));
|
const result = await actions.delete(actionEvent('delete', {}));
|
||||||
expect(result).toMatchObject({ status: 500 });
|
expect(result).toMatchObject({ status: 500 });
|
||||||
expect(result.data.error).toBeTruthy();
|
expect(failData(result).error).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import { load, actions } from './+page.server';
|
|||||||
|
|
||||||
const mockFetch = vi.fn() as unknown as typeof fetch;
|
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());
|
beforeEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
function localsWith(perms: string[] | null) {
|
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(post).not.toHaveBeenCalled();
|
||||||
expect(result).toMatchObject({ status: 400 });
|
expect(result).toMatchObject({ status: 400 });
|
||||||
expect(result.data.personIds).toEqual(['p1', 'p2']);
|
expect(failData(result).personIds).toEqual(['p1', 'p2']);
|
||||||
expect(result.data.documentIds).toEqual(['d1']);
|
expect(failData(result).documentIds).toEqual(['d1']);
|
||||||
expect(result.data.titleError).toBeTruthy();
|
expect(failData(result).titleError).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
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: '' }));
|
||||||
expect(result.data.titleError).toBeTruthy();
|
expect(failData(result).titleError).toBeTruthy();
|
||||||
expect(result.data.dateError).toBeTruthy();
|
expect(failData(result).dateError).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('redirects to /persons/{id} when originPersonId is a valid UUID', async () => {
|
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' })
|
saveEvent({ title: 'Umzug', type: 'PERSONAL', eventDate: '1925-04-01' })
|
||||||
);
|
);
|
||||||
expect(result).toMatchObject({ status: 409 });
|
expect(result).toMatchObject({ status: 409 });
|
||||||
expect(result.data.error).toBeTruthy();
|
expect(failData(result).error).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user