From e562b3bbea7155127b80066c466e1920d524c74b Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 2 Jun 2026 19:44:56 +0200 Subject: [PATCH] test: migrate remaining 3 $app/forms consumers to shared mock Completes Phase 1a after the load-bearing ChronikFuerDichBox spec proved the pattern. ChronikFuerDichBox.test and NotificationDropdown.test (rich result-firing interceptors) keep their submit-fired assertions (optimisticMarkRead/MarkAllRead) and use formsMock.setFormResult for the failure branch. NotificationBell.spec used the simpler intercept-only factory and renders no form of its own, so it adopts the shared superset purely as a render-time stub. Part of #560. Co-Authored-By: Claude Opus 4.8 --- .../ChronikFuerDichBox.svelte.test.ts | 30 ++-------------- .../NotificationBell.svelte.spec.ts | 12 ++----- .../NotificationDropdown.svelte.test.ts | 34 +++---------------- 3 files changed, 9 insertions(+), 67 deletions(-) diff --git a/frontend/src/lib/activity/ChronikFuerDichBox.svelte.test.ts b/frontend/src/lib/activity/ChronikFuerDichBox.svelte.test.ts index 2ee844e3..022f598c 100644 --- a/frontend/src/lib/activity/ChronikFuerDichBox.svelte.test.ts +++ b/frontend/src/lib/activity/ChronikFuerDichBox.svelte.test.ts @@ -3,36 +3,12 @@ import { cleanup, render } from 'vitest-browser-svelte'; import { page } from 'vitest/browser'; import ChronikFuerDichBox from './ChronikFuerDichBox.svelte'; import type { NotificationItem } from '$lib/notification/notifications'; +import * as formsMock from '$mocks/$app/forms'; -const mockFormResult = vi.hoisted(() => ({ type: 'success' as string })); - -vi.mock('$app/forms', () => ({ - enhance( - node: HTMLFormElement, - submit?: (opts: { - formData: FormData; - }) => (opts: { - result: { type: string; data?: Record }; - update: () => Promise; - }) => Promise - ) { - const handler = async (e: Event) => { - e.preventDefault(); - const cb = submit?.({ formData: new FormData(node) } as never); - if (typeof cb === 'function') { - await ( - cb as (o: { result: typeof mockFormResult; update: () => Promise }) => Promise - )({ result: mockFormResult, update: async () => {} }); - } - }; - node.addEventListener('submit', handler); - return { destroy: () => node.removeEventListener('submit', handler) }; - } -})); +vi.mock('$app/forms', () => ({ ...formsMock })); afterEach(() => { cleanup(); - mockFormResult.type = 'success'; }); const mention = (overrides: Partial = {}): NotificationItem => ({ @@ -160,7 +136,7 @@ describe('ChronikFuerDichBox', () => { }); it('shows an accessible error banner when the dismiss action returns a failure', async () => { - mockFormResult.type = 'failure'; + formsMock.setFormResult({ type: 'failure' }); render(ChronikFuerDichBox, { props: { unread: [mention({ id: 'err-1' })], diff --git a/frontend/src/lib/notification/NotificationBell.svelte.spec.ts b/frontend/src/lib/notification/NotificationBell.svelte.spec.ts index 0ab79577..bf5f5f62 100644 --- a/frontend/src/lib/notification/NotificationBell.svelte.spec.ts +++ b/frontend/src/lib/notification/NotificationBell.svelte.spec.ts @@ -2,18 +2,10 @@ import { afterEach, describe, it, expect, vi } from 'vitest'; import { cleanup, render } from 'vitest-browser-svelte'; import type { NotificationItem } from '$lib/notification/notifications'; import NotificationBell from './NotificationBell.svelte'; +import * as formsMock from '$mocks/$app/forms'; vi.mock('$app/navigation', () => ({ goto: vi.fn(), beforeNavigate: vi.fn() })); -vi.mock('$app/forms', () => ({ - enhance(node: HTMLFormElement, submit?: (opts: { formData: FormData }) => unknown) { - const handler = (e: Event) => { - e.preventDefault(); - submit?.({ formData: new FormData(node) } as never); - }; - node.addEventListener('submit', handler); - return { destroy: () => node.removeEventListener('submit', handler) }; - } -})); +vi.mock('$app/forms', () => ({ ...formsMock })); const mockNotificationList = vi.hoisted((): { value: NotificationItem[] } => ({ value: [] })); diff --git a/frontend/src/lib/notification/NotificationDropdown.svelte.test.ts b/frontend/src/lib/notification/NotificationDropdown.svelte.test.ts index bdbda7eb..7c302562 100644 --- a/frontend/src/lib/notification/NotificationDropdown.svelte.test.ts +++ b/frontend/src/lib/notification/NotificationDropdown.svelte.test.ts @@ -3,41 +3,15 @@ import { cleanup, render } from 'vitest-browser-svelte'; import { page } from 'vitest/browser'; import { goto } from '$app/navigation'; import NotificationDropdown from './NotificationDropdown.svelte'; +import * as formsMock from '$mocks/$app/forms'; vi.mock('$app/navigation', () => ({ goto: vi.fn() })); -// Configurable result for the enhance mock — tests that need failure set -// mockFormResult.type = 'failure' before clicking. -const mockFormResult = vi.hoisted(() => ({ type: 'success' as string })); - -// Invoke the SubmitFunction and always call the returned result callback with -// mockFormResult so tests can exercise both success and failure branches. -vi.mock('$app/forms', () => ({ - enhance( - node: HTMLFormElement, - submit?: (opts: { - formData: FormData; - }) => (opts: { - result: { type: string; data?: Record }; - update: () => Promise; - }) => Promise - ) { - const handler = async (e: Event) => { - e.preventDefault(); - const cb = submit?.({ formData: new FormData(node) } as never); - if (typeof cb === 'function') { - await cb({ result: mockFormResult, update: async () => {} } as never); - } - }; - node.addEventListener('submit', handler); - return { destroy: () => node.removeEventListener('submit', handler) }; - } -})); +vi.mock('$app/forms', () => ({ ...formsMock })); afterEach(() => { cleanup(); vi.clearAllMocks(); - mockFormResult.type = 'success'; // reset to default after each test }); const makeNotification = (overrides: Record = {}) => ({ @@ -235,7 +209,7 @@ describe('NotificationDropdown', () => { }); it('shows a role=alert error banner when mark-all-read returns a failure', async () => { - mockFormResult.type = 'failure'; + formsMock.setFormResult({ type: 'failure' }); render(NotificationDropdown, { props: { notifications: [makeNotification()], @@ -372,7 +346,7 @@ describe('NotificationDropdown', () => { }); it('does NOT call onClose or goto when the dismiss action returns a failure', async () => { - mockFormResult.type = 'failure'; + formsMock.setFormResult({ type: 'failure' }); const onClose = vi.fn(); const n = makeNotification({ id: 'n99', actorName: 'Bob' }); render(NotificationDropdown, {