test(stammbaum): every unit centre sits within its child-units span (#724)
Fixture-wide loop over the canonical forest and a synthetic tree: each unit's run centre is within [min, max] of its child-unit centres — the ancestor centring invariant, asserted on real data. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { buildLayout, NODE_W, NODE_H, COL_GAP, ROW_GAP } from './buildLayout';
|
import { buildLayout, NODE_W, NODE_H, COL_GAP, ROW_GAP } from './buildLayout';
|
||||||
|
import { buildFamilyForest, type Unit } from './familyForest';
|
||||||
import canonicalFixture from '../__fixtures__/stammbaum.json';
|
import canonicalFixture from '../__fixtures__/stammbaum.json';
|
||||||
import type { components } from '$lib/generated/api';
|
import type { components } from '$lib/generated/api';
|
||||||
|
|
||||||
@@ -461,3 +462,43 @@ describe('buildLayout — named-bug guard: deep bloodline (#724)', () => {
|
|||||||
expect(centerX(layout, d)).toBe(mid);
|
expect(centerX(layout, d)).toBe(mid);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Centre-x of a unit's run, derived from its primary's left edge + run width.
|
||||||
|
function unitCenter(layout: ReturnType<typeof buildLayout>, u: Unit): number {
|
||||||
|
const left = layout.positions.get(u.id)!.x;
|
||||||
|
const width = u.members.length * NODE_W + (u.members.length - 1) * COL_GAP;
|
||||||
|
return left + width / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('buildLayout — ancestor centring invariant (#724)', () => {
|
||||||
|
const fixtureNodes = canonicalFixture.nodes as unknown as PersonNodeDTO[];
|
||||||
|
const fixtureEdges = canonicalFixture.edges as unknown as RelationshipDTO[];
|
||||||
|
|
||||||
|
it('every unit centre sits within its child units span (canonical + synthetic)', () => {
|
||||||
|
const cases: [string, PersonNodeDTO[], RelationshipDTO[]][] = [
|
||||||
|
['canonical', fixtureNodes, fixtureEdges],
|
||||||
|
[
|
||||||
|
'synthetic',
|
||||||
|
[node('R', 'R', 0), node('c1', 'c1', 1), node('c2', 'c2', 1), node('c3', 'c3', 1)],
|
||||||
|
[parentEdge('R', 'c1'), parentEdge('R', 'c2'), parentEdge('R', 'c3')]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [label, nodes, edges] of cases) {
|
||||||
|
const layout = buildLayout(nodes, edges);
|
||||||
|
const forest = buildFamilyForest(nodes, edges);
|
||||||
|
const walk = (u: Unit) => {
|
||||||
|
if (u.children.length > 0) {
|
||||||
|
const childCenters = u.children.map((c) => unitCenter(layout, c));
|
||||||
|
const lo = Math.min(...childCenters);
|
||||||
|
const hi = Math.max(...childCenters);
|
||||||
|
const c = unitCenter(layout, u);
|
||||||
|
expect(c, `${label}: unit ${u.id} centred in child span`).toBeGreaterThanOrEqual(lo);
|
||||||
|
expect(c, `${label}: unit ${u.id} centred in child span`).toBeLessThanOrEqual(hi);
|
||||||
|
}
|
||||||
|
u.children.forEach(walk);
|
||||||
|
};
|
||||||
|
forest.roots.forEach(walk);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user