fix(stammbaum): make gutter visibility prop-overridable for tests (#689)
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m45s
CI / OCR Service Tests (pull_request) Successful in 23s
CI / Backend Unit Tests (pull_request) Successful in 3m54s
CI / fail2ban Regex (pull_request) Successful in 44s
CI / Semgrep Security Scan (pull_request) Successful in 20s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m3s
CI / Unit & Component Tests (push) Successful in 3m49s
CI / OCR Service Tests (push) Successful in 23s
CI / Backend Unit Tests (push) Successful in 4m14s
CI / fail2ban Regex (push) Successful in 47s
CI / Semgrep Security Scan (push) Successful in 22s
CI / Compose Bucket Idempotency (push) Successful in 1m3s

CI kept failing on the two gutter-render tests because the vitest-browser
iframe viewport is narrower than 768 px → window.matchMedia(min-width:
768px) returns false → gutter is hidden → g[role="text"] selector
returns []. The previous synchronous-seed fix was insufficient because
matchMedia itself was the false branch.

Add an optional `showGutter?: boolean` prop. When set, it bypasses the
matchMedia detection — tests pass `showGutter: true` to assert the
rendered gutter, and `showGutter: false` to assert the absent path.
Production callers leave it undefined so the existing media-query
detection still governs visibility.

Refs #689

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit was merged in pull request #690.
This commit is contained in:
Marcel
2026-05-28 16:53:27 +02:00
parent f124529ee8
commit a5e3205520
2 changed files with 28 additions and 30 deletions

View File

@@ -18,9 +18,16 @@ interface Props {
selectedId: string | null;
zoom: number;
onSelect: (id: string) => void;
/**
* Force-show or force-hide the generation gutter. When undefined, falls
* back to a `window.matchMedia('(min-width: 768px)')` detection so the
* gutter only appears on md+ viewports. Tests pass an explicit boolean
* to avoid depending on the vitest-browser iframe viewport.
*/
showGutter?: boolean;
}
let { nodes, edges, selectedId, zoom, onSelect }: Props = $props();
let { nodes, edges, selectedId, zoom, onSelect, showGutter }: Props = $props();
const layout = $derived.by<Layout>(() => buildLayout(nodes, edges));
@@ -39,13 +46,15 @@ let isMdOrUp = $state(
: false
);
$effect(() => {
if (showGutter !== undefined) return;
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return;
const mq = window.matchMedia(GUTTER_MEDIA_QUERY);
const handler = (e: MediaQueryListEvent) => (isMdOrUp = e.matches);
mq.addEventListener('change', handler);
return () => mq.removeEventListener('change', handler);
});
const gutterWidth = $derived(isMdOrUp ? GUTTER_WIDTH_DESKTOP : 0);
const gutterVisible = $derived(showGutter ?? isMdOrUp);
const gutterWidth = $derived(gutterVisible ? GUTTER_WIDTH_DESKTOP : 0);
type GutterRow = { rank: number; y: number; label: number | null };
const gutterRows = $derived.by<GutterRow[]>(() => {