diff --git a/docs/adr/012-browser-test-mocking-strategy.md b/docs/adr/012-browser-test-mocking-strategy.md index 0e33b309..d916fc10 100644 --- a/docs/adr/012-browser-test-mocking-strategy.md +++ b/docs/adr/012-browser-test-mocking-strategy.md @@ -79,7 +79,7 @@ The following `vi.mock(module, factory)` calls in browser specs are **acceptable These modules are resolved at static import time (before any test runs). Their `vi.mock` factories are served by birpc synchronously during module graph resolution, not after worker teardown. -**Pattern note:** When an overlay or dropdown contains a navigation link (``), use `e.preventDefault()` + `goto(path)` in the click handler instead of letting the browser follow the `href`. In a vitest-browser Playwright iframe there is no SvelteKit router, so a real navigation tears down the orchestrator iframe and crashes the test run. The `href` attribute should still be present for right-click / open-in-new-tab semantics. +**Pattern note:** When an overlay or dropdown triggers a navigation action, use ` diff --git a/frontend/src/lib/notification/NotificationDropdown.svelte.test.ts b/frontend/src/lib/notification/NotificationDropdown.svelte.test.ts index e72573be..03e72e9d 100644 --- a/frontend/src/lib/notification/NotificationDropdown.svelte.test.ts +++ b/frontend/src/lib/notification/NotificationDropdown.svelte.test.ts @@ -6,7 +6,10 @@ import NotificationDropdown from './NotificationDropdown.svelte'; vi.mock('$app/navigation', () => ({ goto: vi.fn() })); -afterEach(cleanup); +afterEach(() => { + cleanup(); + vi.clearAllMocks(); +}); const makeNotification = (overrides: Record = {}) => ({ id: 'n1', @@ -156,7 +159,7 @@ describe('NotificationDropdown', () => { expect(onMarkAllRead).toHaveBeenCalledOnce(); }); - it('calls onClose when the view-all link is clicked', async () => { + it('calls onClose when the view-all button is clicked', async () => { const onClose = vi.fn(); render(NotificationDropdown, { props: { @@ -167,14 +170,44 @@ describe('NotificationDropdown', () => { } }); - const viewAllLink = page.getByRole('link', { name: /alle aktivitäten|view all/i }); - await expect.element(viewAllLink).toHaveAttribute('href', '/aktivitaeten'); - await viewAllLink.click(); + await page.getByRole('button', { name: /alle aktivitäten|view all/i }).click(); expect(onClose).toHaveBeenCalledOnce(); + }); + + it('navigates to /aktivitaeten when the view-all button is clicked', async () => { + render(NotificationDropdown, { + props: { + notifications: [], + onMarkRead: () => {}, + onMarkAllRead: () => {}, + onClose: () => {} + } + }); + + await page.getByRole('button', { name: /alle aktivitäten|view all/i }).click(); + expect(goto).toHaveBeenCalledWith('/aktivitaeten'); }); + it('calls onClose before navigating to /aktivitaeten', async () => { + const callOrder: string[] = []; + const onClose = vi.fn(() => callOrder.push('close')); + vi.mocked(goto).mockImplementation(() => callOrder.push('goto')); + render(NotificationDropdown, { + props: { + notifications: [], + onMarkRead: () => {}, + onMarkAllRead: () => {}, + onClose + } + }); + + await page.getByRole('button', { name: /alle aktivitäten|view all/i }).click(); + + expect(callOrder).toEqual(['close', 'goto']); + }); + it('renders MENTION items with the mention verb text', async () => { render(NotificationDropdown, { props: {