Uh oh!
There was an error while loading. Please reload this page.
feat(scripts): gate vi.mock specifiers that resolve to no file - #6198
Merged
Conversation
A `vi.mock` / `vi.doMock` whose relative specifier names no file on disk does not error. Vitest registers the mock against a module id nothing imports, the run proceeds with the real module everywhere, and the suite passes — with no warning and no smaller assertion count, identically to a correct one. Adds `scripts/check-vi-mock-specifiers.mjs`: walks every tracked JS/TS-family source file, finds each mock call site, and resolves the relative specifiers against the calling file's own directory. Resolution matches the repo's real specifier style (extension ladder, /index.* forms, NodeNext trailing-.js strip) and judges with isFile rather than a bare existence test. The gate is green at rest, so two things keep it from being indistinguishable from a gate that does nothing: the verdict line carries the census, and the scan FAILS when its population collapses to nothing. Wired as a cheap-tier gate in its own unfiltered workflow — checkout plus one node call, no install and no build.
…oors
The inferred types from the parameter defaults were `null | undefined` and the
frozen literal shape of FLOORS, so a fixture caller passing its own file list
or `{}` for the floors failed `tsc -p tsconfig.scripts.json`.yinlianghui-tw
marked this pull request as ready for review
August 25, 2026 00:35
yinlianghui-tw
enabled auto-merge
August 25, 2026 00:35
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#5646
A
vi.mock/vi.doMockwhose relative specifier names no file on disk does not error. Vitest registers the mock against a module id nothing imports, the run proceeds with the real module everywhere, and the suite passes — no warning, no smaller assertion count, identical green to a correct one. Same family as #4347 (a declaration pointing at nothing, reported as a pass), one layer down.Adds
scripts/check-vi-mock-specifiers.mjs, its test, and the workflow wiring.The evidence, because this gate is GREEN AT REST
There are zero unresolvable specifiers in the tree, so a green run proves nothing on its own. The ablation is the evidence the gate exists. It uses the real historical instance, not a synthetic one.
PR #5645 wrote the runtime-config mock in
packages/app-shell/src/layout/__tests__/ChatDock.partialRuntimeConfig.test.tsxone..short. Reconstructed on the real file at head519ce076b:exit=0✅vi.mock('../../runtime-config'1 → 0;vi.mock('../runtime-config'0 → 1exit=1, 1 finding, naming…ChatDock.partialRuntimeConfig.test.tsx:48git checkout HEAD -- <path>,git diff HEADemptyexit=0✅, identical censusThe mutation is proven by anchored
grep -ccounts, never an editor's exit code. That guard earned its keep on the first attempt: aperl -piwhoses///delimiter collided with the path failed, the counts stayed 2/0, and the run was reported as a no-op instead of a false green.The discriminating half
The neighbours in that same file are what made the bug invisible: they sit at different depths and are both correct. A resolver that got depth wrong would flag them and be deleted by the first person it annoyed. With the defect present, none of them appears in the report:
'../AiUsageIndicator'— one level up → not flagged'../../hooks/surfaceAgent'— two levels up → not flagged'../../hooks'— two levels up, resolved viaindex.ts→ not flaggedRepo-wide, the same half is measured by the census: 675 relative specifiers resolve, 0 unresolvable.
Non-vacuity
The population refuses to collapse. Zero source files, zero test files or zero relative specifiers is a broken walk, not a clean tree — reporting
OKfor it is this gate's own defect one level up.FLOORSmakes each a failure, pinned end-to-end throughmain()'s exit code (a throwaway git repo carrying the import graph and nothing tracked →exit=1,population COLLAPSED), following thetoBeGreaterThan(1000)shape from PR #6195. The verdict line prints the census rather than a bareOK.Census — re-derived, not quoted
The card's figures were three days old. Measured at
519ce076b:Plus, newly separated: 645 bare (out of scope), 0 non-static, 1 embedded in a string literal, 3 via the
import()form.Two corrections to the card
vi.mock(import(…))is not a zero population — the card and the dispatch order both recorded zero; there are three live call sites (plugin-kanban,plugin-calendar,plugin-listregistration suites). What is zero is the intersection this gate judges: that form written with a relative specifier. The pattern covers it for free either way, and the census counts it separately so this stays visible.*.test.*/*.spec.*suffix, and two match no test-file convention at all —vitest.setup.base.tsandpackages/plugin-map/vitest.setup.ts. A setup file is exactly where a repo-wide mock gets written. So the walk takes every tracked JS/TS source file and lets the pattern decide; the test-named count is still derived, reported, and floored.Resolution, and the two things it must not judge
Matches the repo's real specifier style: bare path plus
.ts/.tsx/.js/.jsx/.mjs/.cjs, the/index.*forms, and a trailing.jsstripped and the ladder retried (src/is NodeNext throughout). The judgement isisFile, never a bare existence test — a directory with no index is correctly unresolved, which a bare existence check would wave through.Two classes are counted but never judged, both because they are not text the language executes:
scripts/js-comment-mask.mjs;eslint-rules/no-dynamic-import-in-test-hook.test.js:37— and it is correct as written: aRuleTestercode sample in a template literal, mocking a fictional'./dep'. An ESLint fixture is source text; there is no directory it is relative to and no mock to be inert. The discriminator is thevitoken, not the specifier — nothing about the specifier separates the two spellings.Scope, per the dispatch ruling
vi.mock(import(…))— covered because the same pattern already matches it; no handling invented beyond that.Tier and wiring — confirmed, not assumed
Cheap-gate tier: no install, no build, no vitest run. Measured ~2.7s over 3696 files (the card said ~1s; the honest number is 2.7s, still a checkout plus one
nodecall). Its own unfiltered workflow,vi-mock-specifiers.yml, alongsidecontrol-bytes.yml/skills-paths.yml/pre-install-import-graph.yml.Mechanically confirmed rather than asserted:
check-pre-install-import-graphderives its population from the workflows and now reports 15 pre-install steps in 14 jobs (was 14/13), having picked up this step and walked its import graph — builtins plus repo-relative modules only.Registries updated because a new unfiltered blocking check must be classified, not defaulted into silence:
REQUIRED_CONTEXTSinscripts/dependabot-merge-gate.mjs, themerge_groupreason map inmerge-queue-reporting.test.ts, and a section plus inventory row incontent/docs/guide/ci-cd-pipeline.md.Verification — all at
519ce076bGate list derived from
package.jsonand.github/workflows/, not from the dispatch order. Every exit code captured before any pipe; every verdict below is the line the gate itself printed.Root vitest only (#3378). No changeset:
check-changeset-presencecomputed the diff against the merge-base and reports 0 files of published package source — nothing in this PR ships in a tarball.Generated by Claude Code