feat(person): render precise life dates on cards, hover card, and mention dropdown

Cards compose aria-hidden * / † glyphs in markup so screen readers only
announce the dates; PersonSummaryDTO list card stays year-shaped by
design (ADR-039). MentionDropdown subtitle wraps instead of truncating
so DAY-precision ranges fit at 320px.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-12 18:14:20 +02:00
committed by marcel
parent adac1b1f99
commit 0e7095fee6
10 changed files with 254 additions and 33 deletions

View File

@@ -14,8 +14,10 @@ const AUGUSTE: Person = {
displayName: 'Auguste Raddatz',
personType: 'PERSON',
familyMember: true,
birthYear: 1882,
deathYear: 1944
birthDate: '1882-01-01',
birthDatePrecision: 'YEAR',
deathDate: '1944-01-01',
deathDatePrecision: 'YEAR'
} as unknown as Person;
const POSITION = { top: 100, left: 200 };
@@ -81,18 +83,76 @@ describe('PersonHoverCard — loaded state', () => {
await expect.element(page.getByText('Auguste Raddatz')).toBeInTheDocument();
});
it('renders the life-date range when birthYear and deathYear are present', async () => {
it('renders the life-date range when birth and death dates are present', async () => {
render(PersonHoverCard, {
personId: 'p-aug',
cardId: 'card-1',
position: POSITION,
state: { status: 'loaded', person: AUGUSTE, relationships: [] }
});
await expect.element(page.getByText('* 1882 † 1944')).toBeInTheDocument();
await expect
.element(page.getByTestId('person-hover-card-dates'))
.toHaveTextContent('* 1882 † 1944');
});
it('omits the life-date line when both years are missing', async () => {
const noDates = { ...AUGUSTE, birthYear: undefined, deathYear: undefined } as Person;
it('renders a DAY-precision birth date as a full localized date', async () => {
const exact = {
...AUGUSTE,
birthDate: '1882-03-14',
birthDatePrecision: 'DAY'
} as unknown as Person;
render(PersonHoverCard, {
personId: 'p-aug',
cardId: 'card-1',
position: POSITION,
state: { status: 'loaded', person: exact, relationships: [] }
});
await expect
.element(page.getByTestId('person-hover-card-dates'))
.toHaveTextContent('14. März 1882');
});
it('renders APPROX-precision legacy dates with the ca. prefix', async () => {
const approx = {
...AUGUSTE,
birthDate: '1882-01-01',
birthDatePrecision: 'APPROX',
deathDate: undefined,
deathDatePrecision: 'UNKNOWN'
} as unknown as Person;
render(PersonHoverCard, {
personId: 'p-aug',
cardId: 'card-1',
position: POSITION,
state: { status: 'loaded', person: approx, relationships: [] }
});
await expect.element(page.getByTestId('person-hover-card-dates')).toHaveTextContent('ca. 1882');
});
it('keeps the * and † glyphs out of the accessible text via aria-hidden', async () => {
render(PersonHoverCard, {
personId: 'p-aug',
cardId: 'card-1',
position: POSITION,
state: { status: 'loaded', person: AUGUSTE, relationships: [] }
});
const hidden = [
...document.querySelectorAll(
'[data-testid="person-hover-card-dates"] span[aria-hidden="true"]'
)
].map((el) => el.textContent?.trim());
expect(hidden).toContain('*');
expect(hidden).toContain('†');
});
it('omits the life-date line when both dates are missing', async () => {
const noDates = {
...AUGUSTE,
birthDate: undefined,
birthDatePrecision: 'UNKNOWN',
deathDate: undefined,
deathDatePrecision: 'UNKNOWN'
} as unknown as Person;
render(PersonHoverCard, {
personId: 'p-aug',
cardId: 'card-1',