Files
familienarchiv/frontend/src/lib/components/ContributorStack.svelte.spec.ts
Marcel 55ce696428 fix(dashboard): fix ContributorStack each-block key and add accessible avatar labels
- Replace (actor.name ?? actor.initials + i) with (actor.initials + '-' + actor.color)
  to fix operator-precedence bug that made keys order-dependent when name is null
- Add role="img" + aria-label={actor.name ?? actor.initials} so screen readers
  and touch users can access contributor names

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 07:45:16 +02:00

51 lines
2.0 KiB
TypeScript

import { describe, it, expect, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import ContributorStack from './ContributorStack.svelte';
import type { components } from '$lib/generated/api';
type ActivityActorDTO = components['schemas']['ActivityActorDTO'];
afterEach(() => cleanup());
const makeActor = (overrides: Partial<ActivityActorDTO> = {}): ActivityActorDTO => ({
initials: 'MR',
color: '#7a4f9a',
name: 'Max Raddatz',
...overrides
});
describe('ContributorStack', () => {
it('contributor avatar is announced by screen readers with actor name', async () => {
const actor = makeActor({ name: 'Anna Meier', initials: 'AM' });
render(ContributorStack, { contributors: [actor], hasMore: false });
await expect.element(page.getByRole('img', { name: 'Anna Meier' })).toBeInTheDocument();
});
it('falls back to initials as accessible name when actor name is null', async () => {
const actor = makeActor({ name: undefined, initials: 'AM' });
render(ContributorStack, { contributors: [actor], hasMore: false });
await expect.element(page.getByRole('img', { name: 'AM' })).toBeInTheDocument();
});
it('renders two avatars without crashing when actors have identical initials', async () => {
const actors = [
makeActor({ name: undefined, initials: 'AM', color: '#aa0000' }),
makeActor({ name: undefined, initials: 'AM', color: '#0000bb' })
];
render(ContributorStack, { contributors: actors, hasMore: false });
await expect.element(page.getByText('AM').first()).toBeInTheDocument();
});
it('renders overflow indicator when hasMore is true', async () => {
render(ContributorStack, { contributors: [makeActor()], hasMore: true });
await expect.element(page.getByText('…')).toBeInTheDocument();
});
it('renders empty placeholder when no contributors', async () => {
render(ContributorStack, { contributors: [], hasMore: false });
await expect.element(page.getByTitle('Noch niemand angefangen')).toBeInTheDocument();
});
});