diff --git a/frontend/src/lib/shared/primitives/BackButton.svelte.test.ts b/frontend/src/lib/shared/primitives/BackButton.svelte.test.ts new file mode 100644 index 00000000..f6f8330d --- /dev/null +++ b/frontend/src/lib/shared/primitives/BackButton.svelte.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import BackButton from './BackButton.svelte'; + +afterEach(cleanup); + +describe('BackButton', () => { + it('renders the visible label by default (showLabel=true)', async () => { + render(BackButton, { props: {} }); + + await expect.element(page.getByRole('button', { name: /^zurück$/i })).toBeVisible(); + }); + + it('hides the visible label when showLabel is false', async () => { + render(BackButton, { props: { showLabel: false } }); + + const btn = (await page.getByRole('button').element()) as HTMLButtonElement; + // The label is exposed via aria-label only when showLabel=false. + expect(btn.getAttribute('aria-label')).toBe('Zurück'); + expect(btn.textContent?.trim()).toBe(''); + }); + + it('does not set an aria-label when the visible label is shown', async () => { + render(BackButton, { props: { showLabel: true } }); + + const btn = (await page.getByRole('button').element()) as HTMLButtonElement; + expect(btn.getAttribute('aria-label')).toBeNull(); + }); + + it('applies the supplied class string to the button', async () => { + render(BackButton, { props: { class: 'custom-class' } }); + + const btn = (await page.getByRole('button').element()) as HTMLButtonElement; + expect(btn.classList.contains('custom-class')).toBe(true); + }); + + it('calls history.back() when clicked', async () => { + const backSpy = vi.spyOn(globalThis.history, 'back').mockImplementation(() => {}); + try { + render(BackButton, { props: {} }); + + await page.getByRole('button').click(); + + expect(backSpy).toHaveBeenCalledOnce(); + } finally { + backSpy.mockRestore(); + } + }); +}); diff --git a/frontend/src/lib/shared/primitives/OverflowPillButton.svelte.test.ts b/frontend/src/lib/shared/primitives/OverflowPillButton.svelte.test.ts new file mode 100644 index 00000000..7765d229 --- /dev/null +++ b/frontend/src/lib/shared/primitives/OverflowPillButton.svelte.test.ts @@ -0,0 +1,61 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import OverflowPillButton from './OverflowPillButton.svelte'; + +afterEach(cleanup); + +const persons = [ + { id: 'p1', firstName: 'Anna', lastName: 'Schmidt', displayName: 'Anna Schmidt' }, + { id: 'p2', firstName: 'Bert', lastName: 'Meier', displayName: 'Bert Meier' } +]; + +describe('OverflowPillButton', () => { + it('renders the +N pill labelled with the count', async () => { + render(OverflowPillButton, { props: { extraCount: 3, persons } }); + + await expect.element(page.getByText(/\+3/)).toBeVisible(); + }); + + it('starts with aria-expanded=false', async () => { + render(OverflowPillButton, { props: { extraCount: 2, persons } }); + + await expect + .element(page.getByRole('button', { name: /weitere empfänger/i })) + .toHaveAttribute('aria-expanded', 'false'); + }); + + it('opens the dropdown when the pill is clicked', async () => { + render(OverflowPillButton, { props: { extraCount: 2, persons } }); + + await page.getByRole('button', { name: /weitere empfänger/i }).click(); + + await expect + .element(page.getByRole('button', { name: /weitere empfänger/i })) + .toHaveAttribute('aria-expanded', 'true'); + }); + + it('renders one link per person inside the open dropdown', async () => { + render(OverflowPillButton, { props: { extraCount: 2, persons } }); + + await page.getByRole('button', { name: /weitere empfänger/i }).click(); + + await expect + .element(page.getByRole('link', { name: 'Anna Schmidt' })) + .toHaveAttribute('href', '/persons/p1'); + await expect + .element(page.getByRole('link', { name: 'Bert Meier' })) + .toHaveAttribute('href', '/persons/p2'); + }); + + it('closes the dropdown when Escape is pressed', async () => { + render(OverflowPillButton, { props: { extraCount: 2, persons } }); + + const btn = page.getByRole('button', { name: /weitere empfänger/i }); + await btn.click(); + const btnEl = (await btn.element()) as HTMLButtonElement; + btnEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })); + + await expect.element(btn).toHaveAttribute('aria-expanded', 'false'); + }); +});