From 515fa030887844809e849a6267770e6159114d60 Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 29 Apr 2026 09:04:02 +0200 Subject: [PATCH] test(person-mention): replace setTimeout waits with vi.waitFor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sara #1 + Felix #4: setTimeout(r, 50) and setTimeout(r, 5) were racing the microtask queue — passes on a fast laptop, will fail on a loaded CI runner. Replace all six occurrences with vi.waitFor(() => expect(...)) which polls until the assertion passes (default 1s timeout, 10ms interval). Tests are now deterministic — they pass the moment the condition is true, fail the moment the timeout elapses, and never spuriously time out on slow CI hardware. Co-Authored-By: Claude Sonnet 4.6 --- .../TranscriptionReadView.svelte.test.ts | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/frontend/src/lib/components/TranscriptionReadView.svelte.test.ts b/frontend/src/lib/components/TranscriptionReadView.svelte.test.ts index 5b849ed7..31e62d47 100644 --- a/frontend/src/lib/components/TranscriptionReadView.svelte.test.ts +++ b/frontend/src/lib/components/TranscriptionReadView.svelte.test.ts @@ -280,12 +280,13 @@ describe('TranscriptionReadView — person-mention rendering', () => { const link = document.querySelector('a.person-mention')!; link.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true })); - await new Promise((r) => setTimeout(r, 10)); - const personFetches = fetchMock.mock.calls.filter((c) => - String(c[0]).includes(`/api/persons/${PERSON_ID}`) - ); - expect(personFetches.length).toBeGreaterThanOrEqual(1); + await vi.waitFor(() => { + const personFetches = fetchMock.mock.calls.filter((c) => + String(c[0]).includes(`/api/persons/${PERSON_ID}`) + ); + expect(personFetches.length).toBeGreaterThanOrEqual(1); + }); }); it('deduplicates fetches for the same personId across multiple mouseenter events (B15.5)', async () => { @@ -308,12 +309,12 @@ describe('TranscriptionReadView — person-mention rendering', () => { // Plus a re-hover on the first links[0].dispatchEvent(new MouseEvent('mouseenter', { bubbles: true })); - await new Promise((r) => setTimeout(r, 10)); - - const personFetches = fetchMock.mock.calls.filter( - (c) => String(c[0]) === `/api/persons/${PERSON_ID}` - ); - expect(personFetches.length).toBe(1); + await vi.waitFor(() => { + const personFetches = fetchMock.mock.calls.filter( + (c) => String(c[0]) === `/api/persons/${PERSON_ID}` + ); + expect(personFetches.length).toBe(1); + }); }); it('mounts the hover card on mouseenter when the fetch loads', async () => { @@ -349,9 +350,10 @@ describe('TranscriptionReadView — person-mention rendering', () => { const link = document.querySelector('a.person-mention')!; link.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true })); - await new Promise((r) => setTimeout(r, 50)); - const card = document.querySelector('[data-testid="person-hover-card"]'); - expect(card).not.toBeNull(); + await vi.waitFor(() => { + const card = document.querySelector('[data-testid="person-hover-card"]'); + expect(card).not.toBeNull(); + }); }); it('unmounts the hover card on mouseleave', async () => { @@ -362,12 +364,12 @@ describe('TranscriptionReadView — person-mention rendering', () => { const link = document.querySelector('a.person-mention')!; link.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true })); - await new Promise((r) => setTimeout(r, 5)); link.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true })); - await new Promise((r) => setTimeout(r, 5)); - const card = document.querySelector('[data-testid="person-hover-card"]'); - expect(card).toBeNull(); + await vi.waitFor(() => { + const card = document.querySelector('[data-testid="person-hover-card"]'); + expect(card).toBeNull(); + }); }); it('mounts the hover card on focusin so keyboard users see the preview (WCAG 2.1.1)', async () => { @@ -468,13 +470,15 @@ describe('TranscriptionReadView — person-mention rendering', () => { const link = document.querySelector('a.person-mention')!; link.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true })); - await new Promise((r) => setTimeout(r, 50)); + + await vi.waitFor(() => { + // Anchor is marked as deleted so subsequent hovers/clicks treat it as plain text + const stillLink = document.querySelector('a.person-mention')!; + expect(stillLink.getAttribute('data-person-deleted')).toBe('true'); + }); // 404 → no card mounted const card = document.querySelector('[data-testid="person-hover-card"]'); expect(card).toBeNull(); - // Anchor is marked as deleted so subsequent hovers/clicks treat it as plain text - const stillLink = document.querySelector('a.person-mention')!; - expect(stillLink.getAttribute('data-person-deleted')).toBe('true'); }); });