test(stammbaum): tidyTree nests deep and shallow siblings without overlap (#724)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,36 @@ function center(x: Map<string, number>, n: TidyNode): number {
|
|||||||
return x.get(n.id)! + n.width / 2;
|
return x.get(n.id)! + n.width / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Walk a forest, recording each node's tree depth and width.
|
||||||
|
function depths(roots: TidyNode[]): Map<string, { depth: number; width: number }> {
|
||||||
|
const out = new Map<string, { depth: number; width: number }>();
|
||||||
|
const walk = (n: TidyNode, d: number) => {
|
||||||
|
out.set(n.id, { depth: d, width: n.width });
|
||||||
|
for (const c of n.children) walk(c, d + 1);
|
||||||
|
};
|
||||||
|
for (const r of roots) walk(r, 0);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert no two boxes at the same depth overlap (clearance >= gap).
|
||||||
|
function expectNoOverlap(x: Map<string, number>, roots: TidyNode[], gap: number) {
|
||||||
|
const meta = depths(roots);
|
||||||
|
const ids = [...x.keys()];
|
||||||
|
for (let i = 0; i < ids.length; i++) {
|
||||||
|
for (let j = i + 1; j < ids.length; j++) {
|
||||||
|
const a = meta.get(ids[i])!;
|
||||||
|
const b = meta.get(ids[j])!;
|
||||||
|
if (a.depth !== b.depth) continue;
|
||||||
|
const xa = x.get(ids[i])!;
|
||||||
|
const xb = x.get(ids[j])!;
|
||||||
|
const lo = Math.min(xa, xb);
|
||||||
|
const hi = Math.max(xa, xb);
|
||||||
|
const loW = xa <= xb ? a.width : b.width;
|
||||||
|
expect(hi - lo).toBeGreaterThanOrEqual(loW + gap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe('tidyTree — leaf base case', () => {
|
describe('tidyTree — leaf base case', () => {
|
||||||
it('a single leaf lays out at x = 0', () => {
|
it('a single leaf lays out at x = 0', () => {
|
||||||
const a = leaf('a');
|
const a = leaf('a');
|
||||||
@@ -45,3 +75,25 @@ describe('tidyTree — ancestor centring', () => {
|
|||||||
expect(Math.abs(x.get('c2')! - x.get('c1')!)).toBeGreaterThanOrEqual(W + GAP);
|
expect(Math.abs(x.get('c2')! - x.get('c1')!)).toBeGreaterThanOrEqual(W + GAP);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('tidyTree — contour nesting', () => {
|
||||||
|
it('a deep subtree and a shallow sibling nest without overlap', () => {
|
||||||
|
// root
|
||||||
|
// ├─ a (leaf)
|
||||||
|
// └─ b ─ b1, b2 (deeper)
|
||||||
|
// The contour push must keep b's whole subtree clear of leaf a, and a
|
||||||
|
// clear of b's grandchildren, at every depth.
|
||||||
|
const a = leaf('a');
|
||||||
|
const b = node('b', [leaf('b1'), leaf('b2')]);
|
||||||
|
const root = node('root', [a, b]);
|
||||||
|
const x = layoutForest([root], GAP);
|
||||||
|
|
||||||
|
expectNoOverlap(x, [root], GAP);
|
||||||
|
// Each parent still centred over its own children.
|
||||||
|
expect(center(x, b)).toBe(
|
||||||
|
(center(x, { id: 'b1', width: W, children: [] }) +
|
||||||
|
center(x, { id: 'b2', width: W, children: [] })) /
|
||||||
|
2
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user