+import * as m from '$lib/paraglide/messages.js';
+import TimelineView from '$lib/timeline/TimelineView.svelte';
+import type { PageData } from './$types';
+
+let { data }: { data: PageData } = $props();
+
+
+
+ {m.timeline_heading()}
+
+
+
+
{m.timeline_heading()}
+
+
diff --git a/frontend/src/routes/zeitstrahl/page.server.test.ts b/frontend/src/routes/zeitstrahl/page.server.test.ts
new file mode 100644
index 00000000..e7613eec
--- /dev/null
+++ b/frontend/src/routes/zeitstrahl/page.server.test.ts
@@ -0,0 +1,72 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+
+vi.mock('$lib/shared/api.server', () => ({
+ createApiClient: vi.fn(),
+ extractErrorCode: (e: unknown) => (e as { code?: string } | undefined)?.code
+}));
+
+import { load } from './+page.server';
+import { createApiClient } from '$lib/shared/api.server';
+import { getErrorMessage } from '$lib/shared/errors';
+
+beforeEach(() => vi.clearAllMocks());
+
+const TIMELINE = { years: [{ year: 1914, entries: [] }], undated: [] };
+
+function mockApi(opts: { ok?: boolean; status?: number; data?: unknown; error?: unknown }) {
+ const { ok = true, status = 200, data = TIMELINE, error } = opts;
+ const GET = vi.fn().mockResolvedValue({
+ response: { ok, status },
+ data: ok ? data : undefined,
+ error
+ });
+ vi.mocked(createApiClient).mockReturnValue({ GET } as unknown as ReturnType<
+ typeof createApiClient
+ >);
+ return GET;
+}
+
+function callLoad() {
+ return load({
+ fetch: vi.fn() as unknown as typeof fetch,
+ url: new URL('http://localhost/zeitstrahl'),
+ request: new Request('http://localhost/zeitstrahl'),
+ route: { id: '/zeitstrahl' },
+ params: {}
+ } as unknown as Parameters[0]);
+}
+
+describe('zeitstrahl +page.server load', () => {
+ it('fetches GET /api/timeline and returns { timeline } on ok (REQ-001/002)', async () => {
+ const GET = mockApi({ data: TIMELINE });
+ const result = await callLoad();
+ expect(GET).toHaveBeenCalledWith('/api/timeline');
+ expect(result).toEqual({ timeline: TIMELINE });
+ });
+
+ it('redirects to /login on 401 (REQ-022)', async () => {
+ mockApi({ ok: false, status: 401 });
+ await expect(callLoad()).rejects.toMatchObject({ status: 302, location: '/login' });
+ });
+
+ it('throws a mapped error on 404 (REQ-022)', async () => {
+ mockApi({ ok: false, status: 404, error: { code: 'TIMELINE_EVENT_NOT_FOUND' } });
+ await expect(callLoad()).rejects.toMatchObject({
+ status: 404,
+ body: { message: getErrorMessage('TIMELINE_EVENT_NOT_FOUND') }
+ });
+ });
+
+ it('throws a mapped error on 500 (REQ-022)', async () => {
+ mockApi({ ok: false, status: 500, error: undefined });
+ await expect(callLoad()).rejects.toMatchObject({ status: 500 });
+ });
+
+ it('throws a mapped FORBIDDEN error on 403 (REQ-022)', async () => {
+ mockApi({ ok: false, status: 403, error: { code: 'FORBIDDEN' } });
+ await expect(callLoad()).rejects.toMatchObject({
+ status: 403,
+ body: { message: getErrorMessage('FORBIDDEN') }
+ });
+ });
+});