From 6f867e5548743020c6fa4f319813009def27a85a Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 27 Apr 2026 19:47:49 +0200 Subject: [PATCH] fix(stammbaum): drop inferred relationships that are already direct A spouse listed as a direct PersonRelationship was also being emitted as an inferred SPOUSE chip below, so the same person appeared twice in the Beziehungen card. Filter the inferred list against the IDs already shown as direct edges before slicing the top 5. Added a component test that renders red without the filter and green with it. Co-Authored-By: Claude Opus 4.7 --- .../[id]/PersonRelationshipsCard.svelte | 5 +- .../PersonRelationshipsCard.svelte.test.ts | 56 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 frontend/src/routes/persons/[id]/PersonRelationshipsCard.svelte.test.ts diff --git a/frontend/src/routes/persons/[id]/PersonRelationshipsCard.svelte b/frontend/src/routes/persons/[id]/PersonRelationshipsCard.svelte index 1dc5b660..4579e762 100644 --- a/frontend/src/routes/persons/[id]/PersonRelationshipsCard.svelte +++ b/frontend/src/routes/persons/[id]/PersonRelationshipsCard.svelte @@ -14,7 +14,10 @@ interface Props { let { personId, relationships, inferredRelationships }: Props = $props(); -const topDerived = $derived(inferredRelationships.slice(0, 5)); +const directOtherIds = $derived(new Set(relationships.map((rel) => otherId(rel)))); +const topDerived = $derived( + inferredRelationships.filter((d) => !directOtherIds.has(d.person.id)).slice(0, 5) +); function chipLabel(rel: RelationshipDTO): string { const viewpointIsSubject = rel.personId === personId; diff --git a/frontend/src/routes/persons/[id]/PersonRelationshipsCard.svelte.test.ts b/frontend/src/routes/persons/[id]/PersonRelationshipsCard.svelte.test.ts new file mode 100644 index 00000000..b0a13ef6 --- /dev/null +++ b/frontend/src/routes/persons/[id]/PersonRelationshipsCard.svelte.test.ts @@ -0,0 +1,56 @@ +import { describe, it, expect } from 'vitest'; +import { render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import PersonRelationshipsCard from './PersonRelationshipsCard.svelte'; + +const PERSON_ID = '00000000-0000-0000-0000-000000000001'; +const SPOUSE_ID = '00000000-0000-0000-0000-000000000002'; + +describe('PersonRelationshipsCard', () => { + it('hides an inferred relationship that is already a direct one', async () => { + render(PersonRelationshipsCard, { + personId: PERSON_ID, + relationships: [ + { + id: 'r1', + personId: PERSON_ID, + relatedPersonId: SPOUSE_ID, + personDisplayName: 'Anna Müller', + relatedPersonDisplayName: 'Bertha Müller', + relationType: 'SPOUSE_OF' + } + ], + inferredRelationships: [ + { + person: { + id: SPOUSE_ID, + displayName: 'Bertha Müller', + familyMember: true + }, + label: 'SPOUSE', + hops: 1 + } + ] + }); + + const matches = await page.getByText('Bertha Müller').all(); + expect(matches).toHaveLength(1); + }); + + it('still renders inferred relationships that are not direct', async () => { + const COUSIN_ID = '00000000-0000-0000-0000-000000000003'; + render(PersonRelationshipsCard, { + personId: PERSON_ID, + relationships: [], + inferredRelationships: [ + { + person: { id: COUSIN_ID, displayName: 'Carla Cousine', familyMember: true }, + label: 'COUSIN', + hops: 4 + } + ] + }); + + await expect.element(page.getByText('Carla Cousine')).toBeInTheDocument(); + }); +});