From b3d49b28d7c1e5f1511bbb16d61d277f9f3d76c2 Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 20 May 2026 00:01:54 +0200 Subject: [PATCH] test(transcription): restore strong one-fetch regression guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sara on PR #629 round 3: the round-2 fix captured the fetch count AFTER typing '@', so a regression that re-introduced the legacy per-keystroke items() callback would have its '@'-keystroke fetch silently absorbed into the baseline. Drop the baseline subtraction and count every /api/persons fetch since render — typing '@' + fill('Walter') must total exactly one fetch. Co-Authored-By: Claude Opus 4.7 --- .../PersonMentionEditor.svelte.spec.ts | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte.spec.ts b/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte.spec.ts index 0620ecfb..6d43d56b 100644 --- a/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte.spec.ts +++ b/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte.spec.ts @@ -219,30 +219,36 @@ describe('PersonMentionEditor — AC-2/3: search input drives fetch', () => { expect(fetchesAfterSearch).toBe(1); }); - it('fires exactly one /api/persons fetch when the user searches for Walter (debounced)', async () => { + it('fires exactly one /api/persons fetch when the user searches for Walter (regression guard)', async () => { + // Regression guard: a previous version of PersonMentionEditor had a + // duplicated `items()` callback in the Tiptap suggestion config that + // fetched per-keystroke in addition to the debounced search-input fetch + // (Markus & Felix round-1). To catch that regression, we must NOT + // subtract any baseline — every fetch from render onwards counts. + // Sara on PR #629 round 3. const fetchMock = vi .fn() .mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue([AUGUSTE]) }); vi.stubGlobal('fetch', fetchMock); renderHost(); - // Open the dropdown first so the search input is reachable. `fill` then - // drives the searchbox in one input event — sidesteps per-keystroke - // debounce timing on CI that Sara flagged on PR #629 round 2. + // Open the dropdown, then drive the search input via fill() — sidesteps + // per-keystroke timing of userEvent.type that Sara flagged round 2. await userEvent.type(page.getByRole('textbox'), '@'); await vi.waitFor(async () => { await expect.element(page.getByRole('searchbox')).toBeVisible(); }); - - const fetchesBeforeSearch = fetchMock.mock.calls.length; - await page.getByRole('searchbox').fill('Walter'); await new Promise((r) => setTimeout(r, SEARCH_DEBOUNCE_MS + POST_DEBOUNCE_SLACK_MS)); - const personsFetches = fetchMock.mock.calls - .slice(fetchesBeforeSearch) - .filter(([url]) => typeof url === 'string' && url.startsWith('/api/persons')); + // No baseline subtraction — count ALL /api/persons fetches since render. + // If the legacy per-keystroke items() callback returns, typing `@` alone + // would already produce one fetch and `fill('Walter')` another, breaking + // this assertion. + const personsFetches = fetchMock.mock.calls.filter( + ([url]) => typeof url === 'string' && url.startsWith('/api/persons') + ); expect(personsFetches.length).toBe(1); });