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

@@ -651,6 +651,8 @@ describe('StammbaumTree node rendering branches', () => {
describe('StammbaumTree generation gutter (#689)', () => {
it('renders a G{n} label per occupied generation row when at least one node carries generation', async () => {
// showGutter overrides the matchMedia detection so the test never
// depends on the vitest-browser iframe viewport width.
render(StammbaumTree, {
nodes: [
{ id: ID_A, displayName: 'Walter', familyMember: true, generation: 2 },
@@ -659,7 +661,8 @@ describe('StammbaumTree generation gutter (#689)', () => {
edges: [],
selectedId: null,
zoom: 1,
onSelect: () => {}
onSelect: () => {},
showGutter: true
});
const labels = Array.from(document.querySelectorAll('g[role="text"]')).map((g) =>
@@ -675,7 +678,8 @@ describe('StammbaumTree generation gutter (#689)', () => {
edges: [],
selectedId: null,
zoom: 1,
onSelect: () => {}
onSelect: () => {},
showGutter: true
});
const g3 = Array.from(document.querySelectorAll('g[role="text"]')).find(
@@ -685,32 +689,17 @@ describe('StammbaumTree generation gutter (#689)', () => {
expect(g3!.querySelector('text')!.textContent).toMatch(/G\s*3/);
});
it('omits the gutter when matchMedia (min-width: 768px) is false', async () => {
const originalMatchMedia = window.matchMedia;
window.matchMedia = ((query: string) => ({
matches: false,
media: query,
onchange: null,
addEventListener: () => {},
removeEventListener: () => {},
addListener: () => {},
removeListener: () => {},
dispatchEvent: () => false
})) as unknown as typeof window.matchMedia;
it('omits the gutter when showGutter is false (mobile breakpoint case)', async () => {
render(StammbaumTree, {
nodes: [{ id: ID_A, displayName: 'Herbert', familyMember: true, generation: 3 }],
edges: [],
selectedId: null,
zoom: 1,
onSelect: () => {},
showGutter: false
});
try {
render(StammbaumTree, {
nodes: [{ id: ID_A, displayName: 'Herbert', familyMember: true, generation: 3 }],
edges: [],
selectedId: null,
zoom: 1,
onSelect: () => {}
});
const labelGroups = Array.from(document.querySelectorAll('g[role="text"]'));
expect(labelGroups).toHaveLength(0);
} finally {
window.matchMedia = originalMatchMedia;
}
const labelGroups = Array.from(document.querySelectorAll('g[role="text"]'));
expect(labelGroups).toHaveLength(0);
});
});