Skip to content

chore(frontend): upgrade Vitest, Playwright, jsdom, Prettier - #84

Merged
0xMMA merged 2 commits into
mainfrom
chore/test-tooling-upgrades
Sep 18, 2026
Merged

0xMMA merged 2 commits into
mainfrom
chore/test-tooling-upgrades

Conversation

@0xMMA

@0xMMA 0xMMA commented Sep 18, 2026 •

Copy link
Copy Markdown
Owner

Refs #35 — E4 step 3. frontend/ devDependencies only; Angular, PrimeNG and TypeScript stay put and wait for #31.

from to note
@playwright/test 1.58.2 1.63.0
jsdom 28.1.0 30.1.0 two majors
prettier 3.8.1 3.9.8
vitest 4.0.8 4.1.11 not 5 — see below

Vitest 5 was tried and rejected, on evidence

The brief asked for 4 → 5. It is not deliverable: @angular/build:unit-test declares vitest as an optional peer at ^4.0.8 — on every published 21.2.x and on 22.1.8, the newest release.

I shipped 5 anyway in the first version of this PR, on evidence that turned out to be about the wrong thing. The test run was fine — clean npm ci, 198 tests, a deliberately broken assertion still failing, and the builder's watch/rerun path working. What it missed is what developers actually do:

$ npm install --dry-run
npm error code ERESOLVE
npm error Found: vitest@5.0.1 … peerOptional vitest@"^4.0.8" from @angular/build@21.2.0

Not a warning — an error with a non-zero exit, for every package anyone tries to add, until they know to pass --legacy-peer-deps. Every CI workflow uses npm ci, which is unaffected, so a green pipeline was never evidence about this. A test-runner major is not worth breaking local installs for.

So the bump stops at 4.1.11: inside the declared range, npm install --dry-run clean, 198 tests green, and clearMocks still defaults to false as on 4.0.8 — no silent semantic change.

The conditions for moving to 5 are recorded in .claude/rules/testing.md rather than lost: the builder has to declare ^5, and the same change has to handle vitest 5 flipping clearMocks to true by default. The builder does not set it, so a mock's calls stop surviving into the next test — an assertion on call counts that should fail could start passing.

Spec files are not isolated — and the rules said the opposite

The first version of this PR added a claim that each spec file gets its own environment. It is false. The builder sets isolate: false, so files in one fork share a process, a globalThis and one jsdom window.

Measured: 11 of 14 files landed in a single fork and saw ResizeObserver and matchMedia already defined on entry, set by two files that ran before them. Which fork a file lands in changes between runs.

That also explains a leak I had reported as "observed once, did not reproduce in ten runs" — it reproduces perfectly; it depends on scheduling. The advice (every file sets its own stub) was right and the reason was wrong, which is the more damaging half: it told the next author that cross-file state cannot happen. .claude/rules/testing.md now describes the mechanism and says to run a new or changed spec on its own once, with the command.

All three load-bearing stubs were checked by removing them: settings.component.spec.ts (56 tests fail), text-enhancement.component.spec.ts (36 fail), cli-probe-startup.spec.ts (3 fail). All 14 files also pass individually, so nothing in the suite currently depends on a leaked global.

jsdom 30 changed nothing we depend on

matchMedia, ResizeObserver and IntersectionObserver are undefined in jsdom 30 — against the library and in a spec run on its own. In a full run a spec may see them anyway, per the section above; the rules file now says so, because "green suite" is not proof that your stub is there.

Prettier 3.9 reformats nothing here

3.8 and 3.9 report the same files under --list-different, and the diff of the two lists is empty. The repo is not Prettier-formatted and Prettier is not enforced anywhere — no format script, no CI step, no hook. No formatting commit, no .prettierrc pin.

Playwright 1.63

Every config key still applies, and each was confirmed by its effect rather than by reading the types: testDir (61 tests in 5 files, all from e2e/), projects ([chromium] prefix), timeout (a hanging test reports "Test timeout of 30000ms exceeded"), retries (CI=1 produces "(retry #1)" and a -retry1/ artefact dir), reporter (list output + playwright-report/, no browser opened), use.baseURL, use.screenshot (attachment on failure), use.trace (trace.zip, only on failure), and webServer.env (probe config whose server echoes the injected value).

angular.json's test target keys are all in @angular/build@21.2.0's schema. include: ["src/**/*.spec.ts"] is load-bearing: the schema default is ["**/*.spec.ts", "**/*.test.ts"] relative to the project root, which would reach the Playwright specs in e2e/.

58 passed, 3 skipped. The suite is flaky in one place — router-leave-settings fails in roughly one full run in three — and that is not from this bump: measured 3 failures in 7 runs on 1.63.0 against 1 in 4 on 1.58.2, same test, same cause. Filed with the one-line fix as #85.

Also filed

Both predate this PR.

Verification

  • npm install --dry-run — clean, exit 0 (the check the first version failed)
  • npm ci from a deleted node_modules — exit 0
  • npm test — 14 files, 198 tests, 0 failures; all 14 files also pass individually
  • npm run build — clean
  • npx playwright test — 58 passed, 3 skipped
  • npx tsc -p tsconfig.spec.json --noEmit — exit 0
  • git status clean after the E2E runs

🤖 Generated with Claude Code

0xMMA and others added 2 commits September 18, 2026 09:25
Refs #35, E4 step 3. devDependencies only — Angular, PrimeNG and TypeScript
stay where they are and wait for #31.

  @playwright/test  1.58.2 -> 1.63.0
  jsdom             28.1.0 -> 30.1.0
  prettier          3.8.1  -> 3.9.8
  vitest            4.0.8  -> 5.0.1

One commit rather than one per tool: no changelog needed adapting. No config
key changed, no spec changed, and the three things that looked like they might
need work turned out not to.

VITEST 5 IS OUTSIDE ANGULAR'S DECLARED PEER RANGE

`@angular/build:unit-test` is what `ng test` drives, and it declares vitest as
an optional peer at `^4.0.8` — on every 21.2.x and on 22.1.8, the newest
published. So this pairing is unsupported upstream, not merely untested, and
`npm install` prints `ERESOLVE overriding peer dependency`.

Shipped anyway, on evidence rather than optimism: `npm ci` from a clean
node_modules resolves it and exits 0, all 14 files and 198 tests pass, the test
count is identical to vitest 4, and a deliberately broken assertion still fails
one test — so the suite is running and biting, not passing for the wrong
reason. The caveat is written into .claude/rules/testing.md next to the runner,
because the peer warning in an install log is otherwise the kind of thing
someone "fixes" by downgrading the wrong package.

PRETTIER 3.9 REFORMATS NOTHING HERE

The brief asked for the formatting change to be kept out of the diff if 3.9
moved anything. It does not: 3.8 and 3.9 report the same 30 files under
`--list-different`, and the sets are identical. The repo is simply not
Prettier-formatted and Prettier is not enforced anywhere, so there is no
formatting commit and no .prettierrc pin.

JSDOM 30 CHANGED NOTHING WE DEPEND ON

`matchMedia`, `ResizeObserver` and `IntersectionObserver` are still undefined —
checked twice, once against the library and once with a throwaway spec inside
the running test environment, because those are not the same thing. The
polyfill rule in .claude/rules/testing.md stands and now says it was verified
against 30, so the next bump does not re-derive it.

PLAYWRIGHT 1.63

Every config key still applies: 58 passed and 3 skipped, exercising webServer,
baseURL, screenshot, trace and the chromium project. One full run of four had
`router-leave-settings` fail; it passes 3 of 3 in isolation and 58/58 on the
next two full runs. That is the flake class the config's own comment describes
and `retries: 1` in CI exists for. Not attributed to 1.63 — establishing that
would need the same repetition on 1.58, which was not done.

Co-authored-by: Michael <0xMMA@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…t isolation

Review round on #84. Two of my own claims in .claude/rules/testing.md were
wrong, and the vitest 5 bump costs more than it buys.

VITEST STAYS ON 4.1.11, NOT 5.0.1

The evidence I shipped it on was about the test RUN — clean `npm ci`, identical
test count, a broken assertion still failing — and all of that holds. It missed
the thing developers actually do: `npm install` fails outright.

  npm error code ERESOLVE
  npm error Found: vitest@5.0.1 ... peerOptional vitest@"^4.0.8" from @angular/build

Not a warning, an error with a non-zero exit, for every package anyone tries to
add, until they know to pass --legacy-peer-deps. CI runs `npm ci` everywhere, so
CI would never have caught it — my green pipeline was not evidence about this.
A test-runner major is not worth breaking local installs for, so the bump stops
at 4.1.11: inside the declared peer range, `npm install --dry-run` clean, 198
tests green, and `clearMocks` still defaults to false as it did on 4.0.8.

The conditions for moving to 5 are written down instead of lost: the builder has
to declare `^5`, and the same change has to handle vitest 5 flipping
`clearMocks` to true by default — the builder does not set it, so a mock's calls
stop surviving into the next test and an assertion on call counts that should
fail could start passing.

SPEC FILES ARE NOT ISOLATED, AND I WROTE THE OPPOSITE

I claimed each spec file gets its own environment. The builder sets
`isolate: false`, so files in one fork share a process, a globalThis and one
jsdom window. Measured: 11 of 14 files landed in a single fork and saw
`ResizeObserver` and `matchMedia` already defined on entry, set by two files
that ran before them.

That also explains the leak I reported as "observed once, did not reproduce in
ten runs" — it reproduces perfectly, it just depends on which fork a file lands
in. The advice was right and the reason was wrong, which is the more damaging
half: it told the next author that cross-file state cannot happen.

The jsdom section is corrected the same way. `matchMedia`, `ResizeObserver` and
`IntersectionObserver` are undefined in jsdom 30 and in a spec run on its own —
but in a full run a spec may see them anyway, so a green suite is not proof that
your stub is there. The rule now says to run a new or changed spec on its own
once, with the command to do it.

Co-authored-by: Michael <0xMMA@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@0xMMA
0xMMA merged commit 65bacca into main Sep 18, 2026
4 checks passed
@0xMMA
0xMMA deleted the chore/test-tooling-upgrades branch September 18, 2026 08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant