test(transcription): polish @mention test docstrings and tighten clip assert

Round-4 polish from Sara (#11199) and Felix (#11186):
- Replace setTimeout(50) in stale-response race with tick() — matches
  round-3 pattern Sara verified in the sticky-takeover test.
- Add intent comment above the "clear input" wait — it is a negative
  assertion that must not be optimised away.
- Tighten displayName-clip assert from <=100 to ===100 so the test
  discriminates "clip works" from "clip works AND nothing weakened it".
- JSDoc POST_DEBOUNCE_SLACK_MS with the calibration rationale.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-20 07:12:48 +02:00
parent 9764ada854
commit 322c418321

View File

@@ -8,6 +8,7 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page, userEvent } from 'vitest/browser';
import { tick } from 'svelte';
import PersonMentionEditorHost from './PersonMentionEditor.test-fixture.svelte';
import type { components } from '$lib/generated/api';
import { m } from '$lib/paraglide/messages.js';
@@ -18,7 +19,12 @@ import { SEARCH_DEBOUNCE_MS } from './mentionConstants';
type Person = components['schemas']['Person'];
type PersonMention = components['schemas']['PersonMention'];
// Slack on top of the debounce to absorb CI jitter (total 500 ms is generous).
/**
* Headroom above SEARCH_DEBOUNCE_MS for the debounce-window wait
* assertions in this file. 350 ms is calibrated against CI-runner jitter
* we observed pre-#629; dropping it below ~200 ms reintroduces flake.
* See PR #629 round-2 review comment #10935 (Sara).
*/
const POST_DEBOUNCE_SLACK_MS = 350;
const AUGUSTE: Person = {
@@ -275,6 +281,9 @@ describe('PersonMentionEditor — AC-2/3: search input drives fetch', () => {
await userEvent.clear(page.getByRole('searchbox'));
// Negative assertion: wait past the debounce window to confirm no
// trailing fetch was scheduled. Removing this wait would mask a
// re-introduction of the keystroke-driven items() fetch.
await new Promise((r) => setTimeout(r, SEARCH_DEBOUNCE_MS + POST_DEBOUNCE_SLACK_MS));
expect(fetchMock.mock.calls.length).toBe(fetchesBeforeClear);
@@ -332,7 +341,10 @@ describe('PersonMentionEditor — stale-response race', () => {
// The stale fetch now resolves with persons. The dropdown must stay empty.
resolveFetch({ ok: true, json: () => Promise.resolve([AUGUSTE]) });
await new Promise((r) => setTimeout(r, 50));
// Flush pending Svelte reactivity so any (non-)update from the stale
// fetch resolution has landed before we assert. expect.element already
// polls, so no fixed-timeout fallback is needed. Sara on PR #629 round 4.
await tick();
await expect.element(page.getByText('Auguste Raddatz')).not.toBeInTheDocument();
});
@@ -528,7 +540,10 @@ describe('PersonMentionEditor — AC-1: typed text as displayName', () => {
await vi.waitFor(() => {
expect(host.snapshot.mentionedPersons).toHaveLength(1);
expect(host.snapshot.mentionedPersons[0].displayName.length).toBeLessThanOrEqual(100);
// Tight assertion: input is 105 chars, cap is exactly 100. Using
// `toHaveLength(100)` discriminates "clip works" from "clip works
// AND nothing weakened it to e.g. 95". Sara on PR #629 round 4.
expect(host.snapshot.mentionedPersons[0].displayName).toHaveLength(100);
});
});