Files
familienarchiv/frontend/src/lib/person/PersonCard.svelte.test.ts
Marcel 79e9cc5a2b fix(persons): key the unconfirmed badge off provisional only
Align PersonCard's "unbestätigt" badge with the authoritative provisional
flag so the badge, the "Zu prüfen (N)" count and the /persons/review triage
list can never disagree. Empty/"?" name handling is now a separate
crash-safety concern: it still routes to the neutral placeholder glyph
(never a "?" initial) but no longer implies a badge on its own.

Refs #667

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 14:10:16 +02:00

88 lines
3.2 KiB
TypeScript

import { describe, it, expect, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import PersonCard from './PersonCard.svelte';
import type { components } from '$lib/generated/api';
type Person = components['schemas']['PersonSummaryDTO'];
const makePerson = (overrides: Partial<Person> = {}): Person => ({
id: 'p-1',
firstName: 'Anna',
lastName: 'Schmidt',
displayName: 'Anna Schmidt',
personType: 'PERSON',
familyMember: false,
provisional: false,
documentCount: 0,
...overrides
});
afterEach(cleanup);
describe('PersonCard — confirmed person', () => {
it('renders the display name', async () => {
render(PersonCard, { props: { person: makePerson() } });
await expect.element(page.getByText('Anna Schmidt')).toBeVisible();
});
it('does not show an unconfirmed badge for a confirmed person', async () => {
render(PersonCard, { props: { person: makePerson() } });
await expect.element(page.getByText('unbestätigt')).not.toBeInTheDocument();
});
});
describe('PersonCard — unconfirmed badge keys off provisional only (badge ⇔ count ⇔ triage parity)', () => {
it('renders without throwing when lastName is null', async () => {
// Before the fix, `lastName[0]` threw at render for a null lastName. Empty-name
// crash-safety is a SEPARATE concern from the badge: the placeholder glyph renders
// regardless, but the "unbestätigt" badge only fires when provisional is true.
const person = makePerson({
lastName: null as unknown as string,
displayName: '?',
provisional: true
});
render(PersonCard, { props: { person } });
// No throw + provisional → the badge is shown.
await expect.element(page.getByText('unbestätigt')).toBeVisible();
});
it('shows an unbestätigt badge for a provisional person', async () => {
render(PersonCard, { props: { person: makePerson({ provisional: true }) } });
await expect.element(page.getByText('unbestätigt')).toBeVisible();
});
it('does NOT show the badge for a "?" name when not provisional', async () => {
// Empty/"?" name alone is no longer treated as unconfirmed — only `provisional` is.
// This keeps the badge in lockstep with needsReviewCount and the /persons/review list.
render(PersonCard, {
props: {
person: makePerson({ firstName: undefined, lastName: '?', displayName: '?' })
}
});
await expect.element(page.getByText('unbestätigt')).not.toBeInTheDocument();
});
it('does NOT show the badge for an UNKNOWN type when not provisional', async () => {
render(PersonCard, {
props: { person: makePerson({ personType: 'UNKNOWN', displayName: 'Unklar' }) }
});
await expect.element(page.getByText('unbestätigt')).not.toBeInTheDocument();
});
it('renders the placeholder glyph (never a "?" initial) for an empty name even without provisional', async () => {
// Crash-safety branch: a null/empty lastName must not throw and must not show "?".
render(PersonCard, {
props: {
person: makePerson({
firstName: undefined,
lastName: null as unknown as string,
displayName: 'Unbekannt'
})
}
});
await expect.element(page.getByText('Unbekannt')).toBeVisible();
await expect.element(page.getByText('unbestätigt')).not.toBeInTheDocument();
});
});