test(app-shell,console): 29 @object-ui/i18n mock factories inherit the real surface, and the recogniser stops mis-reading a nested generic (objectui#7337) - #7889
Merged
Conversation
…29 mock factories (objectui#7337)
A `vi.mock('@object-ui/i18n', () => ({ … }))` factory that hand-lists its
exports freezes the mock to the names that existed the day it was written.
The next module-scope read of a new export kills the importing file during
COLLECTION, so the suite reports zero failed assertions and the red lands on
an innocent test file.
- convert 29 frozen factories to the obtain-and-spread form;
- delete `apps/console/dev/__tests__/setup/common-mocks.ts`, a mock helper
with zero importers repo-wide carrying the same shape;
- fix the recogniser's nested-generic blind spot: `<[^>]*>` stopped at the
first `>`, so `vi.importActual<Record<string, unknown>>(…)` went unmatched
and four factories that obtain AND spread the real module were reported as
frozen. Repo-wide across all 21 workspace specifiers: 349 frozen -> 344,
with no site moving the other way;
- pin the failure mode itself: three nested vitest runs over a throwaway
package show the frozen factory dying at collection, the converted one
passing, and a lazy read failing as an ordinary assertion.
`COVERED_SPECIFIERS` is deliberately NOT widened: one frozen factory remains
in `DeclaredActionsBar.test.tsx`, held by open PR #7846, and flipping the
ratchet while it exists turns `main` red on merge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDq78vMMSzCGWGmhUYBabh
…mock-i18n-inherit
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This was referenced Sep 6, 2026
…r, not from a coloured summary (objectui#7337) CI run 34003883330 (job 101407488095) failed three assertions in the death pin on output that visibly contained the text they were matching. Under GitHub Actions the nested vitest colours its summary, so the real bytes are `Tests ` + SGR + `1 failed` + SGR + ` (1)` and `\s+` matches no escape sequence. The box this was written on never colours vitest under any env, so the shape was unreachable locally. - the three legs now decide on the JSON reporter's structured counts, so no verdict depends on how vitest PRINTS. Leg 1 asserts total=0 / assertions=0 with the collection error on the SUITE; leg 3 asserts total=1 / assertions=1 with no suite message. That pair is the discrimination, and it is sharper than the prose was, not looser; - the human-readable lines the card quotes are still asserted, on an ANSI-stripped copy; - a new case replays the exact bytes from that CI job: the old regex does not match them, the stripped form does; - `GITHUB_ACTIONS` is dropped from the child's env. Legs 1 and 3 fail on purpose, and with it set the child emitted `::error file=…` annotations into the parent's own CI log — visible in that same run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KDq78vMMSzCGWGmhUYBabh
…mock-i18n-inherit
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
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 free
to 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.
Part of #7337
Part of, not a closing keyword. This PR delivers the sweepand the recogniser repair; it does not add
@object-ui/i18ntoCOVERED_SPECIFIERS, which is that card's headline ask. One frozen factorysurvives, in a file this seat was ordered not to edit, and flipping the ratchet
while it exists turns
mainred on merge. The remaining half is spelled outunder "What is left" below, so the card stays open to carry it.
The real frozen count is 31, and four of the reported 35 were the gate's own bug
Three figures were in circulation before this branch — 92 (a
git grepupperbound), 40/41 (the card, measured on PR #7330's merged head) and 34 (the gate
header's table, measured at
9ce20233f). None of them is what the gate's ownclassifier reads today, and the discrepancy was not only drift.
Running this gate's classifier over
origin/mainwith@object-ui/i18nas thecovered specifier: 93 judged call sites, 58 inheriting, 35 frozen. Four of
those 35 obtain the real module and spread it — they were false positives:
Each writes
const actual = await vi.importActualwith a nested genericargument,
Recordof string to unknown. The recogniser's optional generic wasa character class that excludes the closing angle bracket, so it stopped at the
FIRST one: it consumed the opening bracket plus
Recordplus the innerargument list plus that inner closing bracket, then failed against the outer
closing bracket that follows. The whole call went unmatched and the factory was
reported as one that "never obtains the real module" — on code that obtains it
and spreads it.
That is the failure this gate's own header rules out by name ("an exemption
means the recogniser called correct code broken; fix the recogniser"), and it
stayed invisible because the covered set is
@object-ui/react, where nobodywrites that spelling.
importActualSpans()replaces the regular expression with a balanced scanof the angle brackets. Measured across all 21 workspace specifiers, before and
after, on the same tree:
Zero sites move the other way — the change only ever ADDS a way to recognise
the obtain, so nothing that read
inheritscan start readingfrozen. A fifthfalse positive outside this card's specifier is corrected as a side effect
(
packages/app-shell/src/views/metadata-admin/WidgetContext.catalogUnion.test.tsx:74),which is one fewer entry on #6892's worklist and no sweep of another specifier.
So the genuine frozen population for this specifier was 31, not 35 and not 40.
What this PR does
(
async (importOriginal) =>spreadingawait importOriginalofRecordof string to unknown), across@object-ui/app-shell(22) andapps/console(7).apps/console/dev/__tests__/setup/common-mocks.ts. It exportedapplyCommon+ConsoleMocksbehind a frozen@object-ui/i18nfactory andhad zero importers repo-wide — the card's and triage's instruction was
delete-or-wire, and explicitly not convert-and-keep, which would be insuring
dead code.
check-vi-mock-specifiers.mjs's header cited that file as one ofthe two non-test-named files carrying a call site, so its prose is corrected
in the same commit rather than left stale.
packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx:65— see below.
COVERED_SPECIFIERSuntouched.After the sweep: 92 judged call sites, 91 inherit, 1 frozen, 0 unreadable.
The pin reproduces the DEATH, not the spelling
THE DEATHbuilds a throwaway package in a temp directory whose "next export"is the one added the day after the factory was written, and runs vitest for
real against it. Every verdict is read from the child's JSON reporter, so
nothing depends on how vitest prints (see "Patch round" below for why that
sentence is load-bearing):
total: 0,assertions: 0, collection error on the SUITEsuccess: true,total: 1,passed: 1total: 1,assertions: 1,failed: 1, no suite messageLeg 1 is the signature objectui#6768 measured: a red run with zero failed
assertions, which reads as flake and bills the wrong author. Leg 3 is the
non-vacuity control: same missing export, same frozen factory, and a completely
different shape because the read is no longer at module scope. Without it, a
fixture that failed for any reason at all would satisfy leg 1. The
ANSI-stripped human summary (
Failed Suites 1/Tests no tests/Tests 1 passed/Tests 1 failed) is asserted alongside, as belt to that brace.A fourth case asserts the gate's verdict on those same two factories BEFORE
either is run, so the gate's classification is tied to the observed outcome.
Patch round — CI run 34003883330 was red on the death pin, and why
Three assertions failed on output that visibly contained the text they were
matching. Cause, read out of the job log rather than guessed: under GitHub
Actions the nested vitest colours its summary, so the real bytes are
and
\s+matches no escape sequence. The box this was written on never coloursvitest under any env combination tried (
CI,GITHUB_ACTIONS,FORCE_COLOR,FORCE_TTY,--color, a pty — all measured, all uncoloured), so the shape wasunreachable locally. It is reproduced instead by replaying the exact bytes
from that job in a new case: the old regex does not match them, the stripped
form does. That control is permanent and does not depend on CI colouring today.
Two repairs, both in the pin only:
through a regex was the fragile part. The counts are structured now, and the
discrimination got sharper, not looser — proved below.
GITHUB_ACTIONSis dropped from the child's env. Legs 1 and 3 fail onpurpose, and with it set the child switched on vitest's github-actions
reporter and wrote
::error file=/tmp/vi-mock-collection-death-…annotationsinto the parent's own CI log, decorating this PR's run with failures from a
fixture that was behaving correctly. Measured locally: with the variable, one
::errorline; without it, zero.NO_COLOR=1is also set on the child, but nothing relies on it — the strippingand the structured counts are what make the assertions true either way.
Ablation — five legs, each mutated on disk and restored by content
Every leg mutated a committed file, proved the bytes reached disk, ran, then
restored with
git checkout HEAD --and proved the restore by comparing theon-disk hash against the HEAD blob (never by an exit code):
PageView.test.tsx)C is the trap #7861's fourth ablation warns about — a fixture self-consistent
with the thing it checks — and the pin does not stay green through it.
D and E are new this round and are the direct answer to "leg 3 must still
discriminate": swap the read site between the two legs and exactly the leg
whose premise was removed fails. They are not interchangeable, so no assertion
here is satisfied by "a red run" in general.
What is left, and why
⛔
packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx:65is stillfrozen. The dispatch order locked that file and ruled that a correct partial
delivery beats a red
main. PR #7846 has since landed (83fe6e7), so the lock'spremise is gone and the PM has lifted it — but not for this PR. The follow-up
is ruled to happen after this one merges:
which stay as overrides on top of the spread);
the specifier is NOT in COVERED_SPECIFIERS yetcase inscripts/__tests__/check-vi-mock-inherit.test.ts;'@object-ui/i18n'toCOVERED_SPECIFIERS.Until then the ratchet lives in the pin file, one-directionally: it reddens when
a NEW frozen
@object-ui/i18nfactory appears — the defect — and stays greenwhen the held one is fixed, so nobody's unrelated PR pays for finishing this.
Verification
All at
52c43eb(branch head, merged up toorigin/mainwith #7846 and #7880 in it).Gates, each read from its own verdict line, exit code captured before any pipe:
packages/app-shell/tsconfig.jsonexcludes tests, so the converted files arecompiled by
tsc -p tsconfig.test.json, the second half of that package'stype-check. Proved rather than assumed:--listFileson that project lists1043 app-shell source files and names each converted file exactly once.
Lint is a declared narrowing, not a full run:
eslintover the 33 changedfiles reports 0 errors, 268 warnings, every warning a pre-existing
no-explicit-anyin a test body this PR did not add a line ofanyto (thepatch-round file itself: 0 errors, 0 warnings). The receiving population, read
from eslint's own config resolution rather than guessed, is 4319 files; the
flat config declares no
parserOptions.projectand noprojectService, sotype-aware linting is off and this diff cannot move the verdict on any file it
does not touch. CI runs the full farm.
Changeset:
.changeset/7337-vi-mock-i18n-inherit.md, empty frontmatter —check-changeset-presence.mjshas no carve-out for tests undersrc/, and itsown verdict names this as the complete answer. ⛔ The
skip-changesetlabel isdead in this repo (#4912) and was not applied.
In-flight collisions — both resolved
✅ #7880 landed (
b4c5d1b) and is merged in. Its edit toscripts/__tests__/check-vi-mock-inherit.test.ts(+29/-18) auto-merged withno conflict, as predicted: this branch touches that file only at the import
line and in a pure append. The semantic half was re-checked too — #7880 moves
which sites the shared masker delimits, and its own commit message records that
it deliberately left this gate's local
deJsxClosingTagsworkaround in place.Counts re-derived on the merged head are unchanged: 92 judged / 91 inherit /
1 frozen / 0 unreadable.
✅ #7846 landed (
83fe6e7) and is merged in. Its diff does not touch thefrozen factory, so
DeclaredActionsBar.test.tsx:65reads the same before andafter. ⛔
scripts/js-comment-mask.mjsis not touched here.Generated by Claude Code