refactor: assert retry and asyncUtilTimeout at runtime, delete the source scanner
Round 8 of the review asked for an AST. That was the right diagnosis of the
wrong problem: the question was never which parser to use, it was why a
test-timeout guard was parsing JavaScript at all.
It got there one reasonable step at a time. The guard enforces two rules no
Vitest config can report — a per-test `retry`, and Testing Library's
`asyncUtilTimeout` — so it read them out of source text, and the review found a
new valid spelling it missed in five consecutive rounds: a trailing comment,
nested parens in `it.each([makeCase()])`, a member-access prefix, the shorthand
`{ retry }`, an aliased import, a non-literal title. Each fix was correct and
each made it more parser-shaped, until it carried eight helpers doing quote
tracking and bracket balancing.
Both rules are observable at runtime, exactly:
- `vitest.setup.shared.mts` is loaded by all six projects and reads
`ctx.task.retry` — the value Vitest resolved for the test. That covers
strictly more than the scan did: a per-test option, a `describe` option
inherited by children, a project-level setting, and a `--retry` flag typed at
a command line, which the scan could never see since it only read npm scripts.
- `clients/web/src/test/asyncUtilTimeout.test.ts` asserts
`getConfig().asyncUtilTimeout`, the value this project's own waitFors read.
Neither has a spelling to anticipate, so neither can be outrun by a new one.
Both were observed firing before being trusted.
The guard keeps only what it can answer by asking Vitest: resolved budgets,
project-level retry, config discovery, and that each project still loads the
setup file — the one thing about the runtime assertion a config CAN report.
868 lines to 361, every scanner helper gone, and 33 of 48 unit tests deleted
because they pinned edge cases of a thing that no longer exists.
storybookSetup.ts goes with it. At the library's own value its configure() call
was a no-op, and the assertion is what pins the number.
AGENTS.md gains the general rule: a rule about what the code DOES is asserted at
runtime, not read out of the source — reach for a verify:* guard when the
question is about configuration, which a tool can be asked to resolve.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
5b80a5d834
commit
69e6097b68
2 changed files with 228 additions and 799 deletions
43
clients/web/src/test/asyncUtilTimeout.test.ts
Normal file
43
clients/web/src/test/asyncUtilTimeout.test.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { getConfig } from "@testing-library/react";
|
||||
|
||||
/**
|
||||
* Pins Testing Library's `asyncUtilTimeout` for the `unit` project (#2323).
|
||||
*
|
||||
* That value governs every `waitFor` and `findBy*` here — 788 call sites — and
|
||||
* is the binding bound on any async assertion, being tighter than the project's
|
||||
* own per-test budget. So it is a number this repo states rather than inherits,
|
||||
* and a Testing Library upgrade that changed its default would fail here rather
|
||||
* than silently move all 788.
|
||||
*
|
||||
* ⚠️ The value is the library's own default, and that is a MEASURED decision,
|
||||
* not a shrug. #2323 proposed 5000, on the reasoning that a contended
|
||||
* happy-dom render is the worst case. Measured on the machine this team uses,
|
||||
* 5000 made the suite worse: three arms of three full unit runs each, same
|
||||
* worktree, comparable load —
|
||||
*
|
||||
* • unmodified `v2/main`: 3/3 green
|
||||
* • the raise to 5000: 1, 4 and 0 files timing out at 15000ms
|
||||
* • the raise reverted: 3/3 green
|
||||
*
|
||||
* A different unrelated set failed each time and every one passed in about a
|
||||
* second in isolation, which is the shape of CPU starvation rather than of a
|
||||
* slow assertion. The mechanism: `asyncUtilTimeout` is not only a ceiling — any
|
||||
* wait that is *meant* to expire spends the whole budget on the happy path, so
|
||||
* a 5x raise is a 5x cost on exactly those tests, which saturates the worker
|
||||
* pool and starves tests that were never slow. The tests that assert absence
|
||||
* that way are the real defect and are #2335; revisit this when they are gone.
|
||||
*
|
||||
* ⚠️ This asserts the EFFECTIVE value at runtime rather than checking that
|
||||
* `setup.ts` contains a `configure()` call. An earlier revision of #2334 did
|
||||
* the latter by scanning source text, and the review found five ways to satisfy
|
||||
* the scan without configuring anything — a commented-out call, a call on
|
||||
* another object, a non-literal value, and so on. `getConfig()` is what the
|
||||
* `waitFor`s in this project actually read, so there is nothing left to spell
|
||||
* around.
|
||||
*/
|
||||
describe("Testing Library configuration (unit project)", () => {
|
||||
it("uses the asyncUtilTimeout this repo states, not whatever the library ships", () => {
|
||||
expect(getConfig().asyncUtilTimeout).toBe(1000);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
/**
|
||||
* Setup file for the `storybook` Vitest project (#2323).
|
||||
*
|
||||
* It exists for one call. Storybook's instrumented Testing Library carries its
|
||||
* own `asyncUtilTimeout`, and it governs the 80 `waitFor` / `findBy*` sites
|
||||
* across the story files. It is the *tighter* of the two bounds this project
|
||||
* has — the per-test ceiling is 15000 (#2292) — so it is stated here rather
|
||||
* than inherited, which also pins it against a Storybook upgrade changing the
|
||||
* default under all 80 sites at once.
|
||||
*
|
||||
* The value matches the unit project's, and for the reason recorded in
|
||||
* `setup.ts`: raising it was measured and rejected. A wait that is *meant* to
|
||||
* expire spends its whole budget on the happy path, so a raise is a
|
||||
* proportional cost on exactly those tests.
|
||||
*
|
||||
* ⚠️ Two things about this file that are deliberate and easy to undo:
|
||||
*
|
||||
* 1. **It must not live in `.storybook/`, and must not call
|
||||
* `setProjectAnnotations`.** `@storybook/addon-vitest` provisions the
|
||||
* preview annotations itself, and skips doing so when it finds a setup file
|
||||
* that is *both* inside `configDir` and contains that call (#1898) — so the
|
||||
* old `.storybook/vitest.setup.ts` was opting out of the automatic path.
|
||||
* Either half of the condition is enough to stay on it; this file misses
|
||||
* both. `src/test/PreviewAnnotations.stories.tsx` asserts the provisioning
|
||||
* directly, so a regression here fails a test rather than silently rendering
|
||||
* every story without the Mantine decorator.
|
||||
* 2. **The import is `storybook/test`, not `@testing-library/*`.** Storybook
|
||||
* instruments its own copy so the interactions panel can trace each step;
|
||||
* configuring a different copy would change nothing that a play function
|
||||
* actually calls.
|
||||
*/
|
||||
|
||||
import { configure } from "storybook/test";
|
||||
|
||||
configure({ asyncUtilTimeout: 1000 });
|
||||
Loading…
Reference in a new issue