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();
|
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