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>
190 lines
5.4 KiB
TypeScript
190 lines
5.4 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';
|
||
|
||
afterEach(cleanup);
|
||
|
||
const basePerson = {
|
||
id: 'p-1',
|
||
firstName: 'Anna',
|
||
lastName: 'Schmidt',
|
||
displayName: 'Anna Schmidt',
|
||
personType: 'PERSON' as const
|
||
};
|
||
|
||
describe('PersonCard', () => {
|
||
it('renders the displayName as the primary heading', async () => {
|
||
render(PersonCard, { props: { person: basePerson, canWrite: false } });
|
||
|
||
await expect.element(page.getByRole('heading', { name: 'Anna Schmidt' })).toBeVisible();
|
||
});
|
||
|
||
it('renders the title above the name when personType is PERSON and title is set', async () => {
|
||
render(PersonCard, {
|
||
props: { person: { ...basePerson, title: 'Frau Dr.' }, canWrite: false }
|
||
});
|
||
|
||
await expect.element(page.getByText('Frau Dr.')).toBeVisible();
|
||
});
|
||
|
||
it('omits the title for non-PERSON types even if title is set', async () => {
|
||
render(PersonCard, {
|
||
props: {
|
||
person: { ...basePerson, personType: 'INSTITUTION', title: 'Frau Dr.' },
|
||
canWrite: false
|
||
}
|
||
});
|
||
|
||
await expect.element(page.getByText('Frau Dr.')).not.toBeInTheDocument();
|
||
});
|
||
|
||
it('renders the firstName/lastName initials inside the avatar for PERSON type', async () => {
|
||
render(PersonCard, { props: { person: basePerson, canWrite: false } });
|
||
|
||
await expect.element(page.getByText('AS')).toBeVisible();
|
||
});
|
||
|
||
it('falls back to lastName-only initials when firstName is missing', async () => {
|
||
render(PersonCard, {
|
||
props: {
|
||
person: { ...basePerson, firstName: null, displayName: 'Schmidt' },
|
||
canWrite: false
|
||
}
|
||
});
|
||
|
||
await expect.element(page.getByText('SS')).toBeVisible();
|
||
});
|
||
|
||
it('renders the PersonTypeBadge for non-PERSON types', async () => {
|
||
render(PersonCard, {
|
||
props: {
|
||
person: { ...basePerson, personType: 'INSTITUTION', displayName: 'Acme Inc.' },
|
||
canWrite: false
|
||
}
|
||
});
|
||
|
||
await expect.element(page.getByText('Institution')).toBeVisible();
|
||
});
|
||
|
||
it('omits the PersonTypeBadge for PERSON type', async () => {
|
||
render(PersonCard, { props: { person: basePerson, canWrite: false } });
|
||
|
||
await expect.element(page.getByText('Institution')).not.toBeInTheDocument();
|
||
await expect.element(page.getByText('Gruppe')).not.toBeInTheDocument();
|
||
});
|
||
|
||
it('renders the alias in italic typography when alias is provided', async () => {
|
||
render(PersonCard, {
|
||
props: { person: { ...basePerson, alias: 'Annerl' }, canWrite: false }
|
||
});
|
||
|
||
await expect.element(page.getByText(/Annerl/)).toBeVisible();
|
||
});
|
||
|
||
it('renders DAY-precision life dates as full localized dates', async () => {
|
||
render(PersonCard, {
|
||
props: {
|
||
person: {
|
||
...basePerson,
|
||
birthDate: '1901-03-14',
|
||
birthDatePrecision: 'DAY' as const,
|
||
deathDate: '1944-11-02',
|
||
deathDatePrecision: 'DAY' as const
|
||
},
|
||
canWrite: false
|
||
}
|
||
});
|
||
|
||
await expect.element(page.getByText(/14\. März 1901/)).toBeVisible();
|
||
await expect.element(page.getByText(/2\. November 1944/)).toBeVisible();
|
||
});
|
||
|
||
it('wraps the * and † glyphs in aria-hidden spans', async () => {
|
||
const { container } = render(PersonCard, {
|
||
props: {
|
||
person: {
|
||
...basePerson,
|
||
birthDate: '1901-03-14',
|
||
birthDatePrecision: 'DAY' as const,
|
||
deathDate: '1944-11-02',
|
||
deathDatePrecision: 'DAY' as const
|
||
},
|
||
canWrite: false
|
||
}
|
||
});
|
||
|
||
const hidden = [...container.querySelectorAll('span[aria-hidden="true"]')].map((el) =>
|
||
el.textContent?.trim()
|
||
);
|
||
expect(hidden).toContain('*');
|
||
expect(hidden).toContain('†');
|
||
});
|
||
|
||
it('renders birth-only without dash or dagger', async () => {
|
||
const { container } = render(PersonCard, {
|
||
props: {
|
||
person: {
|
||
...basePerson,
|
||
birthDate: '1901-03-14',
|
||
birthDatePrecision: 'DAY' as const
|
||
},
|
||
canWrite: false
|
||
}
|
||
});
|
||
|
||
await expect.element(page.getByText(/14\. März 1901/)).toBeVisible();
|
||
expect(container.textContent).not.toContain('–');
|
||
expect(container.textContent).not.toContain('†');
|
||
});
|
||
|
||
it('renders APPROX-precision legacy dates with the ca. prefix', async () => {
|
||
render(PersonCard, {
|
||
props: {
|
||
person: {
|
||
...basePerson,
|
||
birthDate: '1882-01-01',
|
||
birthDatePrecision: 'APPROX' as const
|
||
},
|
||
canWrite: false
|
||
}
|
||
});
|
||
|
||
await expect.element(page.getByText(/ca\. 1882/)).toBeVisible();
|
||
});
|
||
|
||
it('renders no life-date line when both dates are missing', async () => {
|
||
const { container } = render(PersonCard, {
|
||
props: { person: basePerson, canWrite: false }
|
||
});
|
||
|
||
expect(container.textContent).not.toContain('*');
|
||
expect(container.textContent).not.toContain('†');
|
||
});
|
||
|
||
it('renders the notes section when notes are provided', async () => {
|
||
render(PersonCard, {
|
||
props: {
|
||
person: { ...basePerson, notes: 'Wohnte in Berlin.' },
|
||
canWrite: false
|
||
}
|
||
});
|
||
|
||
await expect.element(page.getByText('Wohnte in Berlin.')).toBeVisible();
|
||
});
|
||
|
||
it('renders the edit link when canWrite is true', async () => {
|
||
render(PersonCard, { props: { person: basePerson, canWrite: true } });
|
||
|
||
await expect
|
||
.element(page.getByRole('link', { name: /bearbeiten/i }))
|
||
.toHaveAttribute('href', '/persons/p-1/edit');
|
||
});
|
||
|
||
it('does not render the edit link when canWrite is false', async () => {
|
||
render(PersonCard, { props: { person: basePerson, canWrite: false } });
|
||
|
||
await expect.element(page.getByRole('link', { name: /bearbeiten/i })).not.toBeInTheDocument();
|
||
});
|
||
});
|