From c1b125bdb212197b34846e8839adabaef6630516 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 4 Jun 2026 13:28:58 +0200 Subject: [PATCH] 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 --- .../genealogy/layout/buildLayout.test.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/frontend/src/lib/person/genealogy/layout/buildLayout.test.ts b/frontend/src/lib/person/genealogy/layout/buildLayout.test.ts index 5fcf7f98..75efeb49 100644 --- a/frontend/src/lib/person/genealogy/layout/buildLayout.test.ts +++ b/frontend/src/lib/person/genealogy/layout/buildLayout.test.ts @@ -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(); + }); +});