From 4c871b0e325a5d707f6654da62445a96db0b13ff Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 6 Sep 2026 03:12:02 +0000 Subject: [PATCH] test(app-shell,scripts): `@object-ui/i18n` joins `COVERED_SPECIFIERS` (objectui#7337) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The last frozen `vi.mock('@object-ui/i18n', …)` factory in the tree — `DeclaredActionsBar.test.tsx` — now obtains the real module and spreads it, so the specifier's frozen population is zero and it joins the gate's covered set in the same PR, which is what the gate's own header requires of a widening. Three steps, in the only order that works: converting first, then flipping. Flipping while a frozen factory remains fails `check:vi-mock-inherit` on the very next run. Measured on this branch, before -> after: `@object-ui/i18n` 92 judged / 91 inherit / 1 frozen -> 92 judged / 92 inherit / 0 frozen; across all 39 workspace specifiers 315 -> 314 frozen with zero sites moving the other way. The gate's verdict line now reads 223 call sites judged (131 + 92), all inheriting. The pin file loses the case that recorded WHY the specifier was not covered yet and gains its positive half: dropping a member of the covered set makes every call site on it unjudged, so the gate would stay green over a population it no longer looks at. One census case that spelled a single specifier was really asserting the SET'S SIZE, and is now derived from `COVERED_SPECIFIERS` itself. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_013uAaxiwgYDybsTNV9xwa1M --- .../7337-i18n-covered-specifier-flip.md | 9 +++ .../__tests__/DeclaredActionsBar.test.tsx | 3 +- .../__tests__/check-vi-mock-inherit.test.ts | 60 +++++++++++-------- scripts/check-vi-mock-inherit.mjs | 16 +++-- 4 files changed, 58 insertions(+), 30 deletions(-) create mode 100644 .changeset/7337-i18n-covered-specifier-flip.md diff --git a/.changeset/7337-i18n-covered-specifier-flip.md b/.changeset/7337-i18n-covered-specifier-flip.md new file mode 100644 index 000000000..d5e1eff94 --- /dev/null +++ b/.changeset/7337-i18n-covered-specifier-flip.md @@ -0,0 +1,9 @@ +--- +--- + +Finish the `@object-ui/i18n` slice of the `check-vi-mock-inherit` ratchet: the +last frozen factory (`DeclaredActionsBar.test.tsx`) obtains and spreads the real +module, and the specifier joins `COVERED_SPECIFIERS`, so the gate now holds the +population at zero instead of a pin file doing it by hand (objectui#7337). Test +and CI-script only; no published behaviour changes and no package is released by +this change. diff --git a/packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx b/packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx index bb75bf7b6..67cdf539b 100644 --- a/packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx +++ b/packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx @@ -62,7 +62,8 @@ vi.mock('../../utils/getIcon', () => ({ getIcon: () => () => null })); // (`useActionTextLocalizer` — the shared action-text resolver the bar calls // since objectui#4265 — is the REAL one from `@object-ui/react`; it reads these // three resolvers plus `pickLocalized`, so the double has to carry all four.) -vi.mock('@object-ui/i18n', () => ({ +vi.mock('@object-ui/i18n', async (importOriginal) => ({ + ...(await importOriginal>()), useObjectLabel: () => ({ actionLabel: (_o: unknown, _n: unknown, fallback: string) => fallback, actionConfirm: (_o: unknown, _n: unknown, fallback?: string) => fallback, diff --git a/scripts/__tests__/check-vi-mock-inherit.test.ts b/scripts/__tests__/check-vi-mock-inherit.test.ts index d34fc6e28..ce38b819c 100644 --- a/scripts/__tests__/check-vi-mock-inherit.test.ts +++ b/scripts/__tests__/check-vi-mock-inherit.test.ts @@ -58,6 +58,11 @@ const Q = String.fromCharCode(39); const COVERED = '@object-ui/react'; +/** Escape a specifier list for embedding in a `RegExp` source. */ +function escapeRe(text: string) { + return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + /** A `vi.mock` call as SOURCE TEXT, unmatchable in this file, matchable on disk. */ const mockCall = (spec: string, factory: string, fn: 'mock' | 'doMock' = 'mock') => `vi.${fn}(${Q}${spec}${Q}, ${factory});`; @@ -636,12 +641,18 @@ describe('repo state — the gate is green on this tree', () => { it('puts the census in the verdict, so a reader sees the population', () => { // "OK" alone is what a gate that does nothing also prints. + // + // The names come from `COVERED_SPECIFIERS`, not from a copy of it: the list + // is GROW-ONLY, and a case spelling one member reads as an assertion about + // the verdict line while actually asserting the set's SIZE — measured, it + // failed on objectui#7337's flip for that reason and nothing else. + const named = COVERED_SPECIFIERS.join(', '); const line = summarise(result); expect(line).toMatch(/\d+ tracked source file\(s\)/); - expect(line).toMatch(new RegExp(`\\d+ call site\\(s\\) on ${COVERED} judged`)); + expect(line).toMatch(new RegExp(`\\d+ call site\\(s\\) on ${escapeRe(named)} judged`)); const out = execFileSync('node', ['scripts/check-vi-mock-inherit.mjs'], { cwd: repoRoot, encoding: 'utf8' }); expect(out).toMatch(/check-vi-mock-inherit: OK/); - expect(out).toContain(`${result.census.covered} call site(s) on ${COVERED} judged`); + expect(out).toContain(`${result.census.covered} call site(s) on ${named} judged`); }); it('needs no install and no build — it is a cheap-tier gate', () => { @@ -724,7 +735,7 @@ describe('wiring — the gate is reachable and every PR shape starts it', () => // objectui#7337 — the `@object-ui/i18n` sweep // --------------------------------------------------------------------------- -/** The specifier objectui#7337 swept. Not in `COVERED_SPECIFIERS` yet — see below. */ +/** The specifier objectui#7337 swept, and the second member of `COVERED_SPECIFIERS`. */ const I18N = '@object-ui/i18n'; /** Classify one factory in isolation against an arbitrary covered specifier. */ @@ -798,29 +809,27 @@ describe('the generic argument NESTS — `vi.importActual { +describe('the sweep — every `@object-ui/i18n` factory inherits, and the specifier is covered', () => { /** * objectui#7337 converted 29 frozen factories and deleted a 30th * (`apps/console/dev/__tests__/setup/common-mocks.ts`, a helper with zero - * importers repo-wide). The 31st — `DeclaredActionsBar.test.tsx` — is held by - * open PR #7846 and could not be touched, so `COVERED_SPECIFIERS` was NOT - * widened: flipping it while a frozen factory remains turns `main` red. - * - * The assertions below are the ratchet in the meantime, and they are - * deliberately ONE-DIRECTIONAL. They redden when a NEW frozen factory appears - * — the defect — and stay green when the held one is fixed, so nobody's - * unrelated PR pays for finishing this. + * importers repo-wide). The 31st — `DeclaredActionsBar.test.tsx` — was held + * by an open PR at the time and kept the population at one, so the sweep + * shipped without widening `COVERED_SPECIFIERS`: flipping it while a frozen + * factory remains fails this gate on the very next run. That file has since + * been converted and the specifier added, in the one PR the gate's own header + * requires — so the assertions below are no longer a stand-in for the ratchet, + * they are the census the ratchet is computed over. */ - /** Held by open PR #7846 at the time of the sweep. */ - const HELD = 'packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx'; - const swept = () => scan(repoRoot, { covered: [I18N], floors: {} }); - it('no `@object-ui/i18n` factory outside the held file freezes the surface', () => { + it('no `@object-ui/i18n` factory freezes the surface', () => { const result = swept(); - const frozen = result.frozen.map((f: { file: string }) => f.file).filter((f: string) => f !== HELD); - expect(frozen, 'convert these to the obtain-and-spread form before adding the specifier').toEqual([]); + expect( + result.frozen.map((f: { file: string; line: number }) => `${f.file}:${f.line}`), + 'convert these to the obtain-and-spread form — the specifier is covered, so the gate reds too', + ).toEqual([]); expect(result.unreadable, 'a factory the gate cannot read is never a pass').toEqual([]); }); @@ -830,17 +839,18 @@ describe('the sweep — every `@object-ui/i18n` factory inherits, bar the one he const census = swept().census; expect(census.covered).toBeGreaterThan(60); expect(census.inherits).toBeGreaterThan(60); - expect(census.covered - census.inherits - census.automock).toBeLessThanOrEqual(1); + expect(census.covered - census.inherits - census.automock).toBe(0); }); - it('the specifier is NOT in COVERED_SPECIFIERS yet, and this is the reason', () => { - // The follow-up, in one line: once PR #7846 lands, convert - // `DeclaredActionsBar.test.tsx:65`, DELETE this case, and add the specifier - // to `COVERED_SPECIFIERS`. Until then the flip reds `main` on merge. + it('the specifier IS in COVERED_SPECIFIERS — the sweep is held by the gate, not by this file', () => { + // The positive half of the pin this replaces. Without it the third step of + // objectui#7337 could be reverted in silence: dropping a member makes every + // i18n call site unjudged, so the gate stays GREEN over a population it no + // longer looks at — the one direction a ratchet must never be free to move. expect( COVERED_SPECIFIERS, - 'a frozen @object-ui/i18n factory still exists — widening the set now reds main', - ).not.toContain(I18N); + 'the sweep landed; removing the specifier retires the ratchet silently', + ).toContain(I18N); }); it('the zero-importer mock helper is gone, not merely unreferenced', () => { diff --git a/scripts/check-vi-mock-inherit.mjs b/scripts/check-vi-mock-inherit.mjs index 39d73f4b4..1cea0ddf4 100644 --- a/scripts/check-vi-mock-inherit.mjs +++ b/scripts/check-vi-mock-inherit.mjs @@ -82,8 +82,8 @@ * - **Workspace specifiers not in `COVERED_SPECIFIERS`.** See below. * * `COVERED_SPECIFIERS` holds the workspace packages whose frozen sites have - * actually been SWEPT to zero. Today that is exactly one, and the reason is - * measured rather than chosen. Running this file's classifier over all 1,499 + * actually been SWEPT to zero. Today that is two, and each joined by sweep + * rather than by judgement. Running this file's classifier over all 1,499 * `vi.mock` call sites in the tree at `9ce20233f`: * * covered set = @object-ui/react (swept by PR #6847) -> 1 frozen @@ -93,7 +93,15 @@ * at 25 and `@object-ui/components` at 22. Import breadth does not separate them * either -- `@object-ui/react` is THIRD by measured import count (576 imports * across 552 files), behind `@object-ui/core` and `@object-ui/types` -- so there - * is no threshold to derive and no honest way to widen the set today. + * was no threshold to derive and no honest way to widen the set that day. + * + * ⚠️ Those per-specifier figures were taken with the FIRST-ANGLE-BRACKET + * generic this file carried until objectui#7337, which mis-read a nested + * `vi.importActual` generic and over-reported `frozen`; re-derive before acting + * on any of them. `@object-ui/i18n` was the second member, swept and added by + * objectui#7337 -- 92 judged call sites, 92 inheriting, 0 frozen at the flip, + * with the all-specifier population moving 315 -> 314 frozen and no site moving + * the other way. * * **The precondition for widening is a sweep, not a judgement.** Convert a * specifier's frozen factories to the inheriting form, confirm this gate reads @@ -174,7 +182,7 @@ import { blank, scanSource } from './js-comment-mask.mjs'; * The workspace packages this gate judges. GROW-ONLY, and a specifier joins it * only after its frozen factories have been swept to zero -- see "Scope" above. */ -export const COVERED_SPECIFIERS = Object.freeze(['@object-ui/react']); +export const COVERED_SPECIFIERS = Object.freeze(['@object-ui/react', '@object-ui/i18n']); /** Files the walk reads at all. */ const SOURCE_FILE_RE = /\.[cm]?[jt]sx?$/;