test(stammbaum): named-bug guard — deep-bloodline apex is centred, not stranded left (#724)

A 5-generation single bloodline fanning out wide at the bottom: the apex
great-great-grandparent (and every ancestor in the chain) sits at the centre of
the descendant span, the exact symptom the old per-generation packer produced
in reverse (apex pinned to the left edge).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-04 13:33:59 +02:00
parent d703c99c25
commit 86abe07b85

View File

@@ -405,3 +405,59 @@ describe('buildLayout — cross-level marriage fallback (#724)', () => {
expect(layout.positions.get(B)).toBeDefined();
});
});
function centerX(layout: ReturnType<typeof buildLayout>, id: string): number {
return layout.positions.get(id)!.x + NODE_W / 2;
}
describe('buildLayout — named-bug guard: deep bloodline (#724)', () => {
// A 5-generation single bloodline whose deepest generation fans out wide.
// The OLD per-generation packer (now removed) stranded the apex ancestor at
// the LEFT edge of its descendants — the Albert/Martin symptom. The bottom-up
// tidy-tree centres every ancestor over the span of its descendants.
const gg = '00000000-0000-0000-0000-0000000000f0'; // G0 great-great-grandparent
const g = '00000000-0000-0000-0000-0000000000f1'; // G1
const p = '00000000-0000-0000-0000-0000000000f2'; // G2
const d = '00000000-0000-0000-0000-0000000000f3'; // G3
const leaves = ['a', 'b', 'c', 'd', 'e'].map(
(s, i) => `00000000-0000-0000-0000-0000000000${(0xf4 + i).toString(16)}`
);
function buildBloodline() {
return buildLayout(
[
node(gg, 'gg', 0),
node(g, 'g', 1),
node(p, 'p', 2),
node(d, 'd', 3),
...leaves.map((id, i) => node(id, `leaf-${i}`, 4))
],
[
parentEdge(gg, g, 'e1'),
parentEdge(g, p, 'e2'),
parentEdge(p, d, 'e3'),
...leaves.map((id, i) => parentEdge(d, id, `el${i}`))
]
);
}
it('great_great_grandparent_is_not_stranded_left_of_descendants', () => {
const layout = buildBloodline();
const leafCenters = leaves.map((id) => centerX(layout, id));
const minLeaf = Math.min(...leafCenters);
const maxLeaf = Math.max(...leafCenters);
const ggX = centerX(layout, gg);
// The apex ancestor sits strictly inside its descendant span — not pinned
// to the left edge as the old packer left it.
expect(ggX).toBeGreaterThan(minLeaf);
expect(ggX).toBeLessThan(maxLeaf);
// In fact it sits exactly at the centre of the descendant fan-out, and so
// does every ancestor in the chain (single-child chains inherit the centre).
const mid = (minLeaf + maxLeaf) / 2;
expect(ggX).toBe(mid);
expect(centerX(layout, g)).toBe(mid);
expect(centerX(layout, p)).toBe(mid);
expect(centerX(layout, d)).toBe(mid);
});
});