Skip to content

chore(deps): vitest 1 -> 4 as one coupled upgrade (supersedes #284, #288) - #298

Merged
rubenvdlinde merged 2 commits into
developmentfrom
chore/vitest-4
Aug 21, 2026
Merged

chore(deps): vitest 1 -> 4 as one coupled upgrade (supersedes #284, #288)#298
rubenvdlinde merged 2 commits into
developmentfrom
chore/vitest-4

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

chore(deps): vitest 1 -> 4 as one coupled upgrade (vite 7, plugin-vue 6, coverage-v8 4)

Combines dependabot #284 (vitest) and #288 (@vitest/coverage-v8). They cannot
land separately: vitest 4.1.11 peers @vitest/coverage-v8: 4.1.11 EXACTLY, so
either PR alone produces a tree npm ci refuses. The peer chain drags in two
more packages, so this is a four-package move:

vitest                 ^1.6.1 -> ^4.1.11
@vitest/coverage-v8    ^1.6.1 -> ^4.1.11   (exact-pinned to vitest)
vite                   ^5.4.0 -> ^7.3.6    (vitest 4 peers ^6 || ^7 || ^8)
@vitejs/plugin-vue     ^5.2.1 -> ^6.0.8    (needed for vite 7)

vite 7, not vite 8, deliberately. vite 8.2.2 is the current latest, but this
repo's .npmrc sets min-release-age=2 — a supply-chain cooldown that makes
the lockfile lag newly published versions ON PURPOSE. Pulling a package inside
its cooldown window is how you get a lock that resolves differently in CI than
locally. vite 7.3.6 is well outside it and satisfies vitest 4's peer range.

Five tests failed, all the same latent race

Three in ManifestDiff.spec.js, one in PageDesignerHost.spec.js, one in
ApplicationDetailHeader.spec.js. None is a vitest bug and none needed a
production change — every one is a test seeding component state while that
component's own mounted-hook fetch is still in flight.

ManifestDiff is the clearest. The test mounts, then immediately does:

await wrapper.setData({ fromBlob: sampleFrom, toBlob: sampleTo })

with a comment claiming this "skips the async fetch". It does not skip it, it
RACES it. The mocked axios resolves {from: null, to: null}, and whichever
settles second wins. Under vitest 1 setData won; under vitest 4 the fetch does,
nulls fromBlob straight back out, and diffParts computes over two empty
strings and returns []. Verified directly rather than guessed — a probe printed
fromBlob: null immediately after an awaited setData, while diffLines itself
was confirmed to be a working function returning correct hunks.

PageDesignerHost is the same shape wearing different clothes: it arms
mockRejectedValueOnce before the mount-time load() has settled, so the
mount's own request eats the rejection and the explicit load() succeeds.
ApplicationDetailHeader assigns wrapper.vm.versions while the mounted hook is
still fetching and assigning that same field.

Fixed by letting the mount settle first (await flushPromises() /
await flush(wrapper)) before seeding, with a comment at each site saying what
the ordering depends on. These tests were correct-by-accident for three major
versions.

before: 3 files failed, 5 tests failed, 1373 passed
after:  141 files passed, 1378 tests passed

The coverage baseline is recalibrated, and that number needs reading carefully

tests/.coverage-baseline.json vitest: 85.18 -> 64.72.

This is NOT 20 points of lost coverage. The suite is identical — 1378 tests
before and after. The instrument changed:

coverage-v8 v1:  src/App.vue = 235 lines   (exactly its `wc -l`)
coverage-v8 v4:  src/App.vue = 24 lines
across src/**:   59,333 -> 10,097 lines, over the same ~200 files

v1 treated every physical line as coverable — blank lines, comments and Vue
template markup included, nearly all of which score covered for free. v4 remaps
through ast-v8-to-istanbul to executable lines only. 64.72% of real statements
is a stricter bar than 89.63% of a file's line count, so the ratchet is being
recalibrated, not relaxed. The baseline file carries a _note explaining this
so nobody "restores" 85.18 — under v4 it is unreachable and meaningless.

Flagging explicitly for review: this repo does NOT currently run the vitest
ratchet in CI. frontend-checks is ["check:manifest", "test:l10n", "check:gitignore", "check:nc-floor", "format"], and enable-coverage-guard: true refers to the separate PHP clover guard (.coverage-baseline = 57.39).
So no CI gate changes either way here — but leaving 85.18 in place would make
the ratchet fail instantly the day someone wires it up, which is why it is
corrected rather than left alone.

Verified with the commands CI runs

npm ci rc=0
npx vitest run 141/141 files, 1378/1378 tests
npm run test:coverage rc=0
npm run test:coverage-ratchet rc=0 (holds at floor)
npm run build rc=0
npm run lint rc=0
npm run stylelint rc=0
npx prettier --check rc=0

Closes #284
Closes #288

Conduction Release Bot added 2 commits August 21, 2026 07:22
… 6, coverage-v8 4)

Combines dependabot #284 (vitest) and #288 (@vitest/coverage-v8). They cannot
land separately: vitest 4.1.11 peers `@vitest/coverage-v8: 4.1.11` EXACTLY, so
either PR alone produces a tree `npm ci` refuses. The peer chain drags in two
more packages, so this is a four-package move:

    vitest                 ^1.6.1 -> ^4.1.11
    @vitest/coverage-v8    ^1.6.1 -> ^4.1.11   (exact-pinned to vitest)
    vite                   ^5.4.0 -> ^7.3.6    (vitest 4 peers ^6 || ^7 || ^8)
    @vitejs/plugin-vue     ^5.2.1 -> ^6.0.8    (needed for vite 7)

vite 7, not vite 8, deliberately. vite 8.2.2 is the current latest, but this
repo's `.npmrc` sets `min-release-age=2` — a supply-chain cooldown that makes
the lockfile lag newly published versions ON PURPOSE. Pulling a package inside
its cooldown window is how you get a lock that resolves differently in CI than
locally. vite 7.3.6 is well outside it and satisfies vitest 4's peer range.

Five tests failed, all the same latent race
-------------------------------------------
Three in ManifestDiff.spec.js, one in PageDesignerHost.spec.js, one in
ApplicationDetailHeader.spec.js. None is a vitest bug and none needed a
production change — every one is a test seeding component state while that
component's own mounted-hook fetch is still in flight.

ManifestDiff is the clearest. The test mounts, then immediately does:

    await wrapper.setData({ fromBlob: sampleFrom, toBlob: sampleTo })

with a comment claiming this "skips the async fetch". It does not skip it, it
RACES it. The mocked axios resolves `{from: null, to: null}`, and whichever
settles second wins. Under vitest 1 setData won; under vitest 4 the fetch does,
nulls `fromBlob` straight back out, and `diffParts` computes over two empty
strings and returns []. Verified directly rather than guessed — a probe printed
`fromBlob: null` immediately after an awaited setData, while `diffLines` itself
was confirmed to be a working function returning correct hunks.

PageDesignerHost is the same shape wearing different clothes: it arms
`mockRejectedValueOnce` before the mount-time `load()` has settled, so the
mount's own request eats the rejection and the explicit `load()` succeeds.
ApplicationDetailHeader assigns `wrapper.vm.versions` while the mounted hook is
still fetching and assigning that same field.

Fixed by letting the mount settle first (`await flushPromises()` /
`await flush(wrapper)`) before seeding, with a comment at each site saying what
the ordering depends on. These tests were correct-by-accident for three major
versions.

    before: 3 files failed, 5 tests failed, 1373 passed
    after:  141 files passed, 1378 tests passed

The coverage baseline is recalibrated, and that number needs reading carefully
-------------------------------------------------------------------------------
tests/.coverage-baseline.json vitest: 85.18 -> 64.72.

This is NOT 20 points of lost coverage. The suite is identical — 1378 tests
before and after. The instrument changed:

    coverage-v8 v1:  src/App.vue = 235 lines   (exactly its `wc -l`)
    coverage-v8 v4:  src/App.vue = 24 lines
    across src/**:   59,333 -> 10,097 lines, over the same ~200 files

v1 treated every physical line as coverable — blank lines, comments and Vue
template markup included, nearly all of which score covered for free. v4 remaps
through `ast-v8-to-istanbul` to executable lines only. 64.72% of real statements
is a stricter bar than 89.63% of a file's line count, so the ratchet is being
recalibrated, not relaxed. The baseline file carries a `_note` explaining this
so nobody "restores" 85.18 — under v4 it is unreachable and meaningless.

Flagging explicitly for review: this repo does NOT currently run the vitest
ratchet in CI. `frontend-checks` is `["check:manifest", "test:l10n",
"check:gitignore", "check:nc-floor", "format"]`, and `enable-coverage-guard:
true` refers to the separate PHP clover guard (`.coverage-baseline` = 57.39).
So no CI gate changes either way here — but leaving 85.18 in place would make
the ratchet fail instantly the day someone wires it up, which is why it is
corrected rather than left alone.

Verified with the commands CI runs
----------------------------------
  npm ci                        rc=0
  npx vitest run                141/141 files, 1378/1378 tests
  npm run test:coverage         rc=0
  npm run test:coverage-ratchet rc=0 (holds at floor)
  npm run build                 rc=0
  npm run lint                  rc=0
  npm run stylelint             rc=0
  npx prettier --check          rc=0

Closes #284
Closes #288
# Conflicts:
#	package-lock.json
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuild @ 0fcfcdb

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
test-l10n
check-gitignore
check-nc-floor
format
composer ✅ 106/106
npm ✅ 625/625
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-21 06:27 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit 8e51411 into development Aug 21, 2026
80 checks passed
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