From 4edddc38e3cebdb2f2d5f87e1005437052a99bae Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sun, 19 Apr 2026 12:47:40 +0200 Subject: [PATCH] refactor(search): runDebounced ohne missweisenden Parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der _q-Parameter wurde nie benutzt — Consumer sollen stattdessen store.query im \$effect lesen, dann runDebounced() callen. Weniger Footgun, explizitere Call-Site. Tests-Rename: "mid-flight" → "cleared/changed", beschreibt was der Test tatsaechlich absichert. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib/client/search.svelte.ts | 2 +- tests/unit/search-store.test.ts | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/lib/client/search.svelte.ts b/src/lib/client/search.svelte.ts index 4121ac9..9272859 100644 --- a/src/lib/client/search.svelte.ts +++ b/src/lib/client/search.svelte.ts @@ -52,7 +52,7 @@ export class SearchStore { this.fetchImpl = opts.fetchImpl ?? ((...a) => fetch(...a)); } - runDebounced(_q: string): void { + runDebounced(): void { if (this.debounceTimer) clearTimeout(this.debounceTimer); if (this.skipNextDebounce) { this.skipNextDebounce = false; diff --git a/tests/unit/search-store.test.ts b/tests/unit/search-store.test.ts index 83689c5..744c457 100644 --- a/tests/unit/search-store.test.ts +++ b/tests/unit/search-store.test.ts @@ -27,7 +27,7 @@ describe('SearchStore', () => { const fetchImpl = mockFetch([]); const store = new SearchStore({ fetchImpl, debounceMs: 50 }); store.query = 'abc'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(100); expect(store.searching).toBe(false); expect(fetchImpl).not.toHaveBeenCalled(); @@ -40,7 +40,7 @@ describe('SearchStore', () => { ]); const store = new SearchStore({ fetchImpl, debounceMs: 50, pageSize: 30 }); store.query = 'pasta'; - store.runDebounced(store.query); + store.runDebounced(); expect(store.searching).toBe(true); await vi.advanceTimersByTimeAsync(100); await vi.waitFor(() => expect(fetchImpl).toHaveBeenCalled()); @@ -58,7 +58,7 @@ describe('SearchStore', () => { ]); const store = new SearchStore({ fetchImpl, debounceMs: 50 }); store.query = 'pizza'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(100); await vi.waitFor(() => expect(store.webHits).toHaveLength(1)); expect(fetchImpl).toHaveBeenCalledTimes(2); @@ -66,7 +66,7 @@ describe('SearchStore', () => { expect(store.webPageno).toBe(1); }); - it('race-guard: fetch response discarded when query changed mid-flight', async () => { + it('race-guard: stale fetch response discarded when query was cleared/changed', async () => { vi.useFakeTimers(); let resolveFetch!: (v: Response) => void; const fetchImpl = vi.fn( @@ -77,7 +77,7 @@ describe('SearchStore', () => { ); const store = new SearchStore({ fetchImpl, debounceMs: 10 }); store.query = 'stale-query'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(15); expect(fetchImpl).toHaveBeenCalledTimes(1); // User keeps typing BEFORE the response arrives — race-guard should kick in @@ -118,7 +118,7 @@ describe('SearchStore', () => { ]); const store = new SearchStore({ fetchImpl, debounceMs: 10, pageSize: 30 }); store.query = 'meal'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(15); await vi.waitFor(() => expect(store.hits).toHaveLength(30)); expect(store.localExhausted).toBe(false); @@ -140,7 +140,7 @@ describe('SearchStore', () => { ]); const store = new SearchStore({ fetchImpl, debounceMs: 10, pageSize: 30 }); store.query = 'soup'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(15); await vi.waitFor(() => expect(store.hits).toHaveLength(1)); expect(store.localExhausted).toBe(true); @@ -159,7 +159,7 @@ describe('SearchStore', () => { ]); const store = new SearchStore({ fetchImpl, debounceMs: 10 }); store.query = 'anything'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(15); await vi.waitFor(() => expect(store.webError).toBe('SearXNG unreachable')); expect(store.webExhausted).toBe(true); @@ -170,7 +170,7 @@ describe('SearchStore', () => { const fetchImpl = mockFetch([]); const store = new SearchStore({ fetchImpl, debounceMs: 100 }); store.query = 'foobar'; - store.runDebounced(store.query); + store.runDebounced(); store.reset(); await vi.advanceTimersByTimeAsync(200); expect(store.query).toBe(''); @@ -195,7 +195,7 @@ describe('SearchStore', () => { store.restoreSnapshot(snap); expect(store.query).toBe('lasagne'); expect(store.hits).toHaveLength(1); - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(100); expect(fetchImpl).not.toHaveBeenCalled(); const round = store.captureSnapshot(); @@ -214,7 +214,7 @@ describe('SearchStore', () => { filterParam: () => '&domains=chefkoch.de' }); store.query = 'curry'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(15); await vi.waitFor(() => expect(fetchImpl).toHaveBeenCalledTimes(2)); expect(fetchImpl.mock.calls[0][0]).toMatch(/&domains=chefkoch\.de/); @@ -236,7 +236,7 @@ describe('SearchStore', () => { filterParam: () => filter }); store.query = 'broth'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(15); filter = '&domains=chefkoch.de'; store.reSearch();