From 33aff368679f3204c3dca13238bdf1b155c7e043 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 14 Jun 2026 20:35:49 +0200 Subject: [PATCH] test(timeline): guard the layer filter against navigation and fetch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A static boundary gate (mirroring the project's no-`{@html}` greps) that reads TimelineFilters.svelte and /zeitstrahl/+page.svelte and fails if either ever reintroduces goto(, url.searchParams, api.GET, or fetch( — the filter must stay presentation-only and fully client-side. Refs #780 Co-Authored-By: Claude Opus 4.8 --- .../timeline/timelineFilterBoundary.spec.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 frontend/src/lib/timeline/timelineFilterBoundary.spec.ts diff --git a/frontend/src/lib/timeline/timelineFilterBoundary.spec.ts b/frontend/src/lib/timeline/timelineFilterBoundary.spec.ts new file mode 100644 index 00000000..0d6d6bf8 --- /dev/null +++ b/frontend/src/lib/timeline/timelineFilterBoundary.spec.ts @@ -0,0 +1,37 @@ +import { describe, it, expect } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; + +// REQ-001/002: the layer filter is presentation-only and fully client-side. It +// must never navigate or fetch — the route derives the filtered view from +// already-loaded data. This static guard mirrors the project's existing +// grep-gates (e.g. the no-`{@html}` checks) and fails the build if a future +// edit reintroduces navigation or a network call into either file. +const read = (relative: string) => + readFileSync(fileURLToPath(new URL(relative, import.meta.url)), 'utf8'); + +const FILES = { + 'TimelineFilters.svelte': read('./TimelineFilters.svelte'), + '/zeitstrahl/+page.svelte': read('../../routes/zeitstrahl/+page.svelte') +}; + +const FORBIDDEN: { label: string; pattern: RegExp }[] = [ + { label: 'goto(', pattern: /\bgoto\s*\(/ }, + { label: 'url.searchParams', pattern: /url\.searchParams/ }, + { label: 'api.GET', pattern: /\bapi\.GET\b/ }, + { label: 'fetch(', pattern: /\bfetch\s*\(/ } +]; + +describe('layer-filter boundary (REQ-001/002)', () => { + for (const [name, source] of Object.entries(FILES)) { + it(`${name} was found and is non-empty`, () => { + expect(source.length).toBeGreaterThan(0); + }); + + for (const { label, pattern } of FORBIDDEN) { + it(`${name} contains no ${label}`, () => { + expect(pattern.test(source), `${name} must not use ${label}`).toBe(false); + }); + } + } +});