fix(test): NotificationDropdown "view-all link" test causes iframe navigation crash in CI #545
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
CI frontend tests fail with an unhandled error in
src/lib/notification/NotificationDropdown.svelte.test.ts:The test
'calls onClose when the view-all link is clicked'does:The component renders:
In the vitest-browser Playwright iframe there is no SvelteKit router, so clicking the anchor triggers a real full-page navigation to
/aktivitaeten. This disconnects the orchestrator iframe and crashes the entire test file (not just the one test case).Secondary noise (no test failures, but logged)
@tailwindcss/vite:generate:servelogs pre-transform errors when it processes Svelte virtual CSS modules forChronikFuerDichBox.svelteandhilfe/transkription/+page.svelte— it sees script/template content instead of CSS. These do not cause test failures but are noisy in CI output.Fix
Two-part change following the existing pattern used in
src/routes/admin/page.svelte.test.ts:1.
NotificationDropdown.svelte— prevent default href navigation and delegate to SvelteKit'sgoto():The
hrefstays for accessibility (right-click / open-in-new-tab);goto()handles client-side navigation in the real app.2.
NotificationDropdown.svelte.test.ts— mock$app/navigationsogotois a no-op spy:Acceptance criteria
npm run testpasses locally with no unhandled errors inNotificationDropdown.svelte.test.ts👨💻 Felix Brandt — Senior Fullstack Developer
Observations
onclickprop currently bound toonClosealone has noe.preventDefault(), so clicking the<a>follows thehreffor real. In the vitest-browser Playwright iframe there is no router — the real navigation disconnects the orchestrator iframe and crashes the entire test file, not just the one test case. That's why CI shows all 11 tests failed.src/routes/admin/page.svelte.test.ts:5-17is the exact reference:vi.mock('$app/navigation', ...)at the top, then import the component. The issue correctly identifies this as the template.NotificationDropdown.sveltewill only importgoto, so{ goto: vi.fn() }is the right-sized mock — don't wholesale copy the admin page's full mock object.vi.mock()before any imports run. The existing static import ofNotificationDropdownat the top of the test file does not need to be converted toawait import().page.getByRole('link')with no name filter. This works while there is exactly one<a>in the dropdown, but is implicitly coupled to that assumption. Adding{ name: /alle aktivitäten|view all/i }(matchingm.chronik_view_all()) makes the intent explicit.expect(onClose).toHaveBeenCalledOnce()but never checks thatgoto('/aktivitaeten')was actually called. Sincegotobecomes avi.fn()spy, the test should also assert the navigation call — otherwise it verifies the callback fires but not that the user ends up anywhere.Recommendations
e.preventDefault(); onClose(); goto('/aktivitaeten'). Close the dropdown before navigation begins.goto()returns a Promise that isn't awaited in the handler, so order determines which side-effect the user perceives first.getByRoleselector with anamefilter as shown above.🏛️ Markus Keller — Application Architect
Observations
notificationmodule. No new cross-domain dependencies, no service boundary crossings, no infrastructure changes.goto()is the idiomatic SvelteKit navigation primitive for this case: the component needs both a realhref(for right-click / middle-click / open-in-new-tab browser behavior) and client-side routing (no full-page reload).e.preventDefault()+goto()is the canonical solution across the SvelteKit ecosystem.gotofrom$app/navigationis a SvelteKit framework dependency, not a cross-domain service call. No module boundary is crossed.Recommendations
🧪 Sara Holt — QA Engineer
Observations
expect(onClose).toHaveBeenCalledOnce()but does not verify thatgoto('/aktivitaeten')was called. Sincegotois avi.fn()spy after the mock, the test can and should assert both sides: the callback and the navigation. Without the navigation assertion, the test would pass even ifgotowere never invoked — which is the core behavioral guarantee of this fix.page.getByRole('link')at line 167 has no name filter. It passes today because the dropdown renders exactly one<a>. Add{ name: /alle aktivitäten|view all/i }(the rendered text ofm.chronik_view_all()) so the test fails meaningfully if the link is renamed or removed, rather than selecting an unintended element.ChronikFuerDichBox.svelteandhilfe/transkription/+page.svelte) is described in the problem statement but has no acceptance criterion and no proposed fix. This creates review ambiguity — a reviewer cannot tell whether a PR that silences the warnings is "more done" or out of scope. Resolve this explicitly (see Open Decisions).goto()call in the component is a new branch. Asserting it in the test keeps branch coverage meaningful rather than inflating it with untested paths.Recommendations
gotofrom the mocked module in the test and assertexpect(goto).toHaveBeenCalledWith('/aktivitaeten')alongside theonCloseassertion.namefilter to thegetByRole('link')selector.Open Decisions
🔐 Nora "NullX" Steiner — Security Engineer
Observations
hrefpreservation is correct. Keepinghref="/aktivitaeten"on the anchor element is the right call — without it the element loses its link semantics and browsers cannot offer right-click / open-in-new-tab behavior. The path is a static internal route with no user-controlled input, so there is no open-redirect risk.goto()routes through SvelteKit's internal router. Any guards defined in+layout.server.tsorhooks.server.tsstill execute on navigation. No auth check is bypassed.e.preventDefault()is narrow in scope. It suppresses the browser's default href-follow action only — it does not suppress any security-relevant event behavior such as CSRF tokens or session handling.{ goto: vi.fn() }replaces the navigation function in the test environment. No production code is weakened.Recommendations
🎨 Leonie Voss — UX Designer & Accessibility Strategist
Observations
href="/aktivitaeten"is the correct UX decision. Without a realhref, users who right-click or middle-click the "view all" link lose native browser link behavior (open in new tab, copy link address). The proposed fix correctly preserveshreffor these cases while overriding left-click with client-side navigation. This is good for all users, especially those accustomed to link conventions.e.preventDefault(); goto('/aktivitaeten'); onClose(). From a UX standpoint,onClose()should come beforegoto(). The dropdown should close immediately on click — if navigation is slow or delayed, leaving the dropdown open while the page transitions creates a disorienting visual state. Recommended order:e.preventDefault(); onClose(); goto('/aktivitaeten').m.chronik_view_all()— a real text label, not an icon alone. Noaria-labeladdition is needed. Screen readers will announce the link text correctly.<a>element is preserved. No brand or layout concerns.Recommendations
e.preventDefault(); onClose(); goto('/aktivitaeten')for better perceived responsiveness.⚙️ Tobias Wendt — DevOps & Platform Engineer
Observations
NotificationDropdown.svelte.test.tsfile — all 11 tests fail in CI, not just the offending one. The frontend test job fails and blocks PR merges until this is fixed.NotificationDropdown.svelteandNotificationDropdown.svelte.test.ts). The existingci.ymlfrontend job runsnpm run test— no changes to the workflow definition are required.@tailwindcss/vite:generate:servepre-transform errors forChronikFuerDichBox.svelteandhilfe/transkription/+page.sveltedon't cause CI failures today, but log noise can obscure real errors — a future real failure in these components could be buried under the existing noise. Worth a dedicated follow-up issue to suppress or fix, but not a blocker for this PR.Recommendations
📋 Elicit — Requirements Engineer
Observations
goto('/aktivitaeten'). A PR that callsonClose()but replaces the link with a<button>would satisfy all three ACs while breaking the navigation entirely. Add a fourth AC: "Clicking the view-all link callsgoto('/aktivitaeten'), verified by avi.fn()spy assertion in the updated test."Recommendations
goto('/aktivitaeten')— verified by spy assertion in the updated test."Open Decisions
🗳️ Decision Queue — Action Required
1 decision needs your input before implementation starts.
Scope
Tailwind CSS pre-transform log noise — The issue body describes
@tailwindcss/vite:generate:serveerrors forChronikFuerDichBox.svelteandhilfe/transkription/+page.svelteas a "secondary noise" problem, but the acceptance criteria are silent on it. Three options:Option (b) is recommended by both Sara and Tobias — it keeps this fix small and CI-unblocking, while ensuring the noise doesn't get forgotten. (Raised by: Sara, Elicit, Tobias)
A) Fix in this PR
Implementation complete — PR #548
All acceptance criteria met. Four atomic commits on
feat/issue-545-notification-dropdown-iframe-fix:Primary fix
NotificationDropdown.svelte— addedimport { goto } from '$app/navigation'and replacedonclick={onClose}on the footer link with:href="/aktivitaeten"is preserved for right-click / open-in-new-tab.onClose()runs beforegoto()so the dropdown disappears immediately on click.NotificationDropdown.svelte.test.ts— three test changes:vi.mock('$app/navigation', () => ({ goto: vi.fn() }))— minimal mock (onlygoto) to prevent real navigation in the Playwright iframegetByRole('link', { name: /alle aktivitäten|view all/i })— explicit selector, no longer implicitly coupled to "exactly one link"expect(goto).toHaveBeenCalledWith('/aktivitaeten')— asserts the behavioural guarantee introduced by this fixSecondary fix (Tailwind CI noise — option A)
ChronikFuerDichBox.svelteandhilfe/transkription/+page.sveltewere the only two Svelte components with non-empty<style>blocks, causing the Svelte vite plugin to create virtual CSS modules that@tailwindcss/vite:generate:servetried (and failed) to process as CSS. Both<style>blocks have been extracted intolayout.css. The animation class was renamedfade-in→chronik-fade-infor safe global scope.Commits
f6a0d7aatest(notification): add goto mock and tighten selector in NotificationDropdown spec0387d51efix(notification): prevent iframe navigation — use goto instead of href follow97bb1ee6fix(style): move ChronikFuerDichBox animation to global CSS to suppress Tailwind noise0a6a3fd0fix(style): move transkription print styles to global CSS to suppress Tailwind noiseAll 13 NotificationDropdown tests pass. 274 test files, 2588 tests total — zero failures.