Files
familienarchiv/frontend/src/routes/documents/page.server.spec.ts
Marcel 10833fbe6b
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m35s
CI / OCR Service Tests (push) Successful in 36s
CI / Backend Unit Tests (push) Failing after 2m53s
CI / Unit & Component Tests (pull_request) Failing after 2m40s
CI / OCR Service Tests (pull_request) Successful in 31s
CI / Backend Unit Tests (pull_request) Failing after 2m46s
feat(frontend): add /documents page with search, filter, and year-card list
- New documents/+page.svelte wires SearchFilterBar + DocumentList with
  URL-driven navigation (goto + SvelteURLSearchParams)
- Reset button in SearchFilterBar now navigates to /documents
- Rename documents/+page.server.spec.ts → page.server.spec.ts to avoid
  SvelteKit route-file conflict on the + prefix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 00:06:25 +02:00

170 lines
5.3 KiB
TypeScript

import { describe, expect, it, vi, beforeEach } from 'vitest';
vi.mock('$lib/api.server', () => ({ createApiClient: vi.fn() }));
import { load } from './+page.server';
import { createApiClient } from '$lib/api.server';
beforeEach(() => vi.clearAllMocks());
function makeUrl(params: Record<string, string | string[]> = {}) {
const url = new URL('http://localhost/documents');
for (const [key, value] of Object.entries(params)) {
if (Array.isArray(value)) {
value.forEach((v) => url.searchParams.append(key, v));
} else {
url.searchParams.set(key, value);
}
}
return url;
}
// ─── search params forwarding ─────────────────────────────────────────────────
describe('documents page load — search params', () => {
it('passes q, from, to to the search API', async () => {
const mockGet = vi.fn().mockResolvedValue({
response: { ok: true, status: 200 },
data: { items: [], total: 0 }
});
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
typeof createApiClient
>);
await load({
url: makeUrl({ q: 'Urlaub', from: '1920-01-01', to: '1950-12-31' }),
fetch: vi.fn() as unknown as typeof fetch
});
expect(mockGet).toHaveBeenCalledWith(
'/api/documents/search',
expect.objectContaining({
params: expect.objectContaining({
query: expect.objectContaining({ q: 'Urlaub', from: '1920-01-01', to: '1950-12-31' })
})
})
);
});
it('passes senderId and receiverId to the search API', async () => {
const mockGet = vi.fn().mockResolvedValue({
response: { ok: true, status: 200 },
data: { items: [], total: 0 }
});
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
typeof createApiClient
>);
await load({
url: makeUrl({ senderId: 'p-1', receiverId: 'p-2' }),
fetch: vi.fn() as unknown as typeof fetch
});
expect(mockGet).toHaveBeenCalledWith(
'/api/documents/search',
expect.objectContaining({
params: expect.objectContaining({
query: expect.objectContaining({ senderId: 'p-1', receiverId: 'p-2' })
})
})
);
});
it('passes sort, dir, tagQ to the search API', async () => {
const mockGet = vi.fn().mockResolvedValue({
response: { ok: true, status: 200 },
data: { items: [], total: 0 }
});
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
typeof createApiClient
>);
await load({
url: makeUrl({ sort: 'TITLE', dir: 'asc', tagQ: 'fam' }),
fetch: vi.fn() as unknown as typeof fetch
});
expect(mockGet).toHaveBeenCalledWith(
'/api/documents/search',
expect.objectContaining({
params: expect.objectContaining({
query: expect.objectContaining({ sort: 'TITLE', dir: 'asc', tagQ: 'fam' })
})
})
);
});
it('returns items and total from the search result', async () => {
const item = {
document: { id: 'd1' },
matchData: {},
completionPercentage: 0,
contributors: []
};
const mockGet = vi.fn().mockResolvedValue({
response: { ok: true, status: 200 },
data: { items: [item], total: 42 }
});
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
typeof createApiClient
>);
const result = await load({
url: makeUrl({ q: 'test' }),
fetch: vi.fn() as unknown as typeof fetch
});
expect(result.items).toHaveLength(1);
expect(result.total).toBe(42);
});
it('returns filter values in the result for pre-filling the UI', async () => {
const mockGet = vi.fn().mockResolvedValue({
response: { ok: true, status: 200 },
data: { items: [], total: 0 }
});
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
typeof createApiClient
>);
const result = await load({
url: makeUrl({ q: 'Urlaub', from: '1920-01-01', sort: 'TITLE', dir: 'asc' }),
fetch: vi.fn() as unknown as typeof fetch
});
expect(result.q).toBe('Urlaub');
expect(result.from).toBe('1920-01-01');
expect(result.sort).toBe('TITLE');
expect(result.dir).toBe('asc');
});
});
// ─── 401 redirect ─────────────────────────────────────────────────────────────
describe('documents page load — auth redirect', () => {
it('redirects to /login when search API returns 401', async () => {
vi.mocked(createApiClient).mockReturnValue({
GET: vi.fn().mockResolvedValue({ response: { ok: false, status: 401 }, data: null })
} as ReturnType<typeof createApiClient>);
await expect(
load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch })
).rejects.toMatchObject({ location: '/login' });
});
});
// ─── network error fallback ───────────────────────────────────────────────────
describe('documents page load — network error fallback', () => {
it('returns error string instead of throwing when API call throws', async () => {
vi.mocked(createApiClient).mockReturnValue({
GET: vi.fn().mockRejectedValue(new Error('Network failure'))
} as ReturnType<typeof createApiClient>);
const result = await load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch });
expect(result.error).toBeTruthy();
expect(result.items).toEqual([]);
});
});