From 1d559013888cccb0822156cb49b9479d8895153d Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 4 Jun 2026 13:38:40 +0200 Subject: [PATCH] test(stammbaum): a bloodline occupies one contiguous band (#724) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No node outside a root's structural subtree may intrude into that bloodline's [minX, maxX] horizontal span — the contiguity guarantee that fixes the smeared bloodline symptom. Co-Authored-By: Claude Opus 4.8 --- .../genealogy/layout/buildLayout.test.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/frontend/src/lib/person/genealogy/layout/buildLayout.test.ts b/frontend/src/lib/person/genealogy/layout/buildLayout.test.ts index cdc5f611..267b1191 100644 --- a/frontend/src/lib/person/genealogy/layout/buildLayout.test.ts +++ b/frontend/src/lib/person/genealogy/layout/buildLayout.test.ts @@ -539,4 +539,46 @@ describe('buildLayout — ancestor centring invariant (#724)', () => { } } }); + + it('a bloodline occupies one contiguous band — no foreign node interleaved', () => { + // Two roots, each its own bloodline. The first fans out two generations + // deep; the contour pack must keep it as one band with nothing from the + // other bloodline wedged inside its horizontal span. + const nodes = [ + node('R1', 'R1', 0), + node('a', 'a', 1), + node('b', 'b', 1), + node('a1', 'a1', 2), + node('R2', 'R2', 0), + node('c', 'c', 1) + ]; + const edges = [ + parentEdge('R1', 'a'), + parentEdge('R1', 'b'), + parentEdge('a', 'a1'), + parentEdge('R2', 'c') + ]; + const layout = buildLayout(nodes, edges); + const forest = buildFamilyForest(nodes, edges); + + // Collect the R1 bloodline: every member of its unit subtree. + const r1 = forest.roots.find((u) => u.id === 'R1')!; + const bloodline = new Set(); + const collect = (u: Unit) => { + u.members.forEach((m) => bloodline.add(m)); + u.children.forEach(collect); + }; + collect(r1); + + const bandXs = [...bloodline].map((id) => layout.positions.get(id)!.x); + const minX = Math.min(...bandXs); + const maxX = Math.max(...bandXs) + NODE_W; + + // No node outside the bloodline may intrude into its [minX, maxX] band. + for (const [id, p] of layout.positions) { + if (bloodline.has(id)) continue; + const intrudes = p.x < maxX && p.x + NODE_W > minX; + expect(intrudes, `foreign node ${id} interleaved into the R1 band`).toBe(false); + } + }); });