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:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user