test(stammbaum): cross-level marriage records a distinct cross-link (#724)

When the two spouses' parents sit at different structural levels, the
structural owner keeps its hierarchy edge and the other parent->spouse edge is
recorded in layout.crossLinks (rendered with a distinct dash). The couple still
sits exactly adjacent in the owner's run and B keeps a real position.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-04 13:28:58 +02:00
committed by marcel
parent e4a9999f2f
commit c1b125bdb2

View File

@@ -364,3 +364,44 @@ describe('buildLayout — multi-spouse ordering (#361)', () => {
} }
}); });
}); });
describe('buildLayout — cross-level marriage fallback (#724)', () => {
// A bond is cross-level when the two spouses' parents sit at different
// structural levels. Adjacency cannot keep both connectors short, so the
// structural owner keeps its hierarchy edge and the other parent → spouse
// edge becomes a distinct cross-link.
const GP = '00000000-0000-0000-0000-0000000000e1'; // G0, deep branch ancestor
const P = '00000000-0000-0000-0000-0000000000e2'; // G1, child of GP
const A = '00000000-0000-0000-0000-0000000000e3'; // G2, child of P (nested deep)
const R = '00000000-0000-0000-0000-0000000000e4'; // G1 root
const B = '00000000-0000-0000-0000-0000000000e5'; // G2, child of R
it('records the displaced parent edge as a cross-link and keeps the couple adjacent', () => {
const layout = buildLayout(
[
node(GP, 'GP', 0),
node(P, 'P', 1),
{ ...node(A, 'A', 2), birthYear: 1900 }, // earlier birth → A is structural owner
node(R, 'R', 1),
{ ...node(B, 'B', 2), birthYear: 1910 }
],
[
parentEdge(GP, P, 'g1'),
parentEdge(P, A, 'g2'),
parentEdge(R, B, 'g3'),
spouseEdge(A, B, 'sp')
]
);
// A owns; B is absorbed into A's run → couple is exactly adjacent.
const posA = layout.positions.get(A)!;
const posB = layout.positions.get(B)!;
expect(posA.y).toBe(posB.y);
expect(Math.abs(posA.x - posB.x)).toBe(NODE_W + COL_GAP);
// R → B is cross-level: it surfaces as a distinct cross-link, and the
// geometry still lands on B's real position (carried redundantly).
expect(layout.crossLinks).toEqual([{ parentId: R, childId: B }]);
expect(layout.positions.get(B)).toBeDefined();
});
});