test+fix(stammbaum): capture script floors >= 1 multi-spouse person (#361)
@Markus + @Tobias + @Sara on PR #693: the multi-spouse property is load-bearing for buildLayout.test.ts (canonical_fixture_assigns_a_position _to_every_node_with_multiple_spouses + canonical_fixture_multi_spouse _falls_through_to_displayName_when_no_fromYear). A recapture against a dataset that lost every multi-spouse person would silently degrade those tests to vacuous truth. Add MIN_MULTI_SPOUSE_PERSONS=1 to the capture-script sanity gates. Extract the validator into a unit-testable TS module next to the fixture; the .mjs script keeps its inline copy (one-file local utility) but the contract is now covered by validateFixture.test.ts. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { validateFixture } from './validateFixture';
|
||||
import canonicalFixture from './stammbaum.json';
|
||||
|
||||
// The fixture validator is the load-bearing contract for the canonical
|
||||
// Stammbaum snapshot: every gate here corresponds to an invariant that
|
||||
// buildLayout.test.ts relies on. Adding or removing a gate without updating
|
||||
// these tests is the failure we want to catch.
|
||||
|
||||
function networkWithNodes(count: number) {
|
||||
return {
|
||||
nodes: Array.from({ length: count }, (_, i) => ({ id: `n${i}`, generation: i % 6 })),
|
||||
edges: []
|
||||
};
|
||||
}
|
||||
|
||||
function spouseEdge(a: string, b: string) {
|
||||
return {
|
||||
id: `${a}|${b}`,
|
||||
personId: a,
|
||||
relatedPersonId: b,
|
||||
personDisplayName: '',
|
||||
relatedPersonDisplayName: '',
|
||||
relationType: 'SPOUSE_OF'
|
||||
};
|
||||
}
|
||||
|
||||
describe('validateFixture', () => {
|
||||
it('passes_for_the_canonical_fixture', () => {
|
||||
expect(() => validateFixture(canonicalFixture)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects_a_fixture_below_the_min_node_floor', () => {
|
||||
expect(() => validateFixture(networkWithNodes(10))).toThrow(/>= 50 nodes/);
|
||||
});
|
||||
|
||||
it('rejects_a_fixture_with_no_multi_spouse_person', () => {
|
||||
// 50 nodes, 5 generations, several SPOUSE_OF edges — but every spouse
|
||||
// edge connects a different pair, so nobody has more than one partner.
|
||||
// Without the multi-spouse floor this would silently pass and the
|
||||
// canonical_fixture_assigns_a_position_to_every_node_with_multiple_spouses
|
||||
// test in buildLayout.test.ts would degrade to vacuous truth.
|
||||
const nodes = Array.from({ length: 50 }, (_, i) => ({ id: `n${i}`, generation: i % 5 }));
|
||||
const edges = [spouseEdge('n0', 'n1'), spouseEdge('n2', 'n3'), spouseEdge('n4', 'n5')];
|
||||
expect(() => validateFixture({ nodes, edges })).toThrow(
|
||||
/>= 1 person with multiple SPOUSE_OF edges/
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts_a_fixture_where_one_person_has_multiple_spouse_edges', () => {
|
||||
const nodes = Array.from({ length: 50 }, (_, i) => ({ id: `n${i}`, generation: i % 5 }));
|
||||
const edges = [spouseEdge('n0', 'n1'), spouseEdge('n0', 'n2')];
|
||||
expect(() => validateFixture({ nodes, edges })).not.toThrow();
|
||||
});
|
||||
|
||||
it('counts_multi_spouse_persons_via_either_edge_direction', () => {
|
||||
const nodes = Array.from({ length: 50 }, (_, i) => ({ id: `n${i}`, generation: i % 5 }));
|
||||
// n5 is the related party in both edges — still counts as multi-spouse.
|
||||
const edges = [spouseEdge('n1', 'n5'), spouseEdge('n2', 'n5')];
|
||||
expect(() => validateFixture({ nodes, edges })).not.toThrow();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user