test(i18n): one marked fake t for the suite, and a guard (#389) #435
No reviewers
Labels
No labels
autopilot:pending-human
source:analyste
source:defenseur
source:human
source:medic
status:approved
status:blocked
status:in-progress
status:needs-clarification
status:needs-fix
status:ready
status:review
status:triage
type:bug
type:feature
type:infra
type:refactor
type:schema
type:security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: maximus/Simpl-Resultat#435
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-389-i18n-mock"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Generated autonomously by /autopilot run of 2026-10-12
Issue #389. Stacked on PR #434 (#409), itself on PR #433 (#408). Base:
issue-409-hook-harness.Summary
One fake
tfor the whole test suite:markedTinsrc/test/i18nMock.tsrenderst(key)ort(key[a=1,b=2]), and the factoryi18nMock({ language?, fixture? })builds thereact-i18nextmodule forvi.mock. All 27 test files that mockedreact-i18nextuse it, and a source guard refuses any local faket. No CHANGELOG entry (tests only), no app code changed.Commits (review order)
test(i18n): add the shared marked fake t...: module, its self-test,echoofuseLicenseMachines.test.tsreplaced bymarkedT.test(i18n): move the raw fake t files...: the 15 raw files, assertions on displayed texts rewritten in marked form, render assertions added where a component had none.test(i18n): replace the local marked fake t copies...: the 12 marked files, copies deleted, assertions unchanged.test(i18n): guard the test sources...:src/__integration__/i18n-mock-guard.test.ts+ the convention indocs/architecture.md(## Tests (vitest sans jsdom)).Inventory drift (re-verified by script on
79ba1ed)27 files, not 24: the 12 raw and 12 marked of the issue are exact, plus 3 raw files added by #423 after the 2026-10-10 inventory (
AddKeywordDialog.render,CategoryDetailPanel.render,CategoryForm.render). They are migrated with the raw files (the criterion "one fake t" and the guard need it).Files
src/test/i18nMock.ts,src/test/i18nMock.test.ts,src/__integration__/i18n-mock-guard.test.tssrc/hooks/useLicenseMachines.test.tsPriceFetchControl,AddKeywordDialog.render,CategoryDetailPanel.render,CategoryForm.render,OnboardingChecklist,ImportEmptyState,SourceList,WizardNavigation,FilterPanel,DocsSubsections,LicenseKeyForm.render,PriceFetchConsentToggle,UpsellCtas.render,TransactionTable.render,DashboardPage.renderBudgetTable.render,BudgetTable.wiring,DashboardEmptyState.render,AppShell.render,OnboardingBanner.render,StepCard.render,LicenseCard.render,BudgetPage.render,BudgetPage.wiring,ImportPage.render,UsersSettingsPage,TransactionsPage.renderdocs/architecture.mdDecisions
t: (matchesuseLicenseContext: () =>,get: (target,select: (q,isParent: (r,useEffect: (fnin 9 files with no faket. A third check: avi.mock("react-i18next", ...)must go throughi18nMock.PriceFetchControl(never rendered before),PriceFetchConsentToggle(renders null while loading under SSR, so rendered through the #409 harness with itsloadingslot preset),CategoryDetailPanel,SourceList+SourceCard, the regionaria-labelofOnboardingChecklist.UpsellCtas.rendercovers four components: one mutation each.DocsSubsections:FIXTUREinvi.hoisted(the factory reads it at load);languageas a getter function forLicenseCardandUpsellCtas;PriceFetchControl's ownkey({json})format replaced by the mark, its fixturei18nKeydata left bare.Proof: one
tremoved from each component (exit code ofnpx vitest run <test file>)balance/PriceFetchControl.tsx{t("balance.priceFetching.button")}→{"balance.priceFetching.button"}categories/AddKeywordDialog.tsxplaceholder={t("transactions.table.noCategory")}→ bare keycategories/CategoryDetailPanel.tsx{t("categories.editCategory")}→ bare keycategories/CategoryForm.tsxariaLabel={t("categories.parent")}→ bare keydashboard/OnboardingChecklist.tsx{t("onboarding.checklist.title")}→ bare keydashboard/OnboardingChecklist.tsxaria-label={t("onboarding.checklist.title")}→ bare keyimport/ImportEmptyState.tsx{t("import.empty.guideLink")}→ bare keyimport/SourceList.tsx{t("import.sources.title")}→ bare keyimport/SourceCard.tsx{t("import.sources.new")}→ bare keyimport/WizardNavigation.tsx{t("common.cancel")}→ bare keyreports/FilterPanel.tsxt("reports.filters.accounts.label")→ bare keysettings/DocsSubsections.tsxt(`docs.${sectionKey}.subsections`, { returnObjects: true })→ the bare templatesettings/LicenseKeyForm.tsx{t(error.i18nKey, error.params)}→{error.i18nKey}settings/PriceFetchConsentToggle.tsx{t("settings.privacy.title")}→ bare keyshared/UpsellGate.tsx{t("upsell.revoked")}→ bare keyshared/PurchaseButton.tsxtier: t("license.editions.base")→ bare keyshared/HaveKeyButton.tsx{t("upsell.ctaHaveKey")}→ bare keyshared/DownloadLatestButton.tsx{t("settings.updates.downloadLatest")}→ bare keytransactions/TransactionTable.tsx<p>{t("transactions.noTransactions")}</p>→ bare keypages/DashboardPage.tsx{t("dashboard.title")}→ bare keyThe first
OnboardingChecklistmutation survived (exit 0): it hit the regionaria-label, which nothing asserted. Assertion added, both mutations now red. Every mutation run once, file restored bygit checkout.Guard proven red (exit 1) twice: a raw local mock put back in
WizardNavigation.test.tsx(2 findings: theuseTranslation:line, and the mock withouti18nMock), andt: markedTreplaced byt: (k: string) => kinuseLicenseMachines.test.ts(1 finding). The guard also has its own unit test on samples (local fake found,useLicenseContext: () =>/get: (targetnot found).Checks
npm run buildgreen;npm test118 files / 2026 tests (base 116 / 2009).For the reviewer
PriceFetchControl.tsxrenders a hard-codedbest-effortbadge, withoutt(i18n rule). Not fixed here (tests-only PR).PriceFetchConsentToggle.test.tsxnow mocksreactwith the harness for the whole file; its older tests do not render and stay green (some of them assert local literals only, untouched).BudgetTable.*,BudgetPage.wiring,UsersSettingsPage) ignored options;markedTnow prints them. No assertion needed a change.🤖 Generated with Claude Code
src/test/i18nMock.ts holds the one fake t of the suite: markedT renders t(key) or t(key[a=1,b=2]), and i18nMock({ language?, fixture? }) builds the react-i18next module for vi.mock. The local echo of useLicenseMachines.test.ts is replaced by markedT. Refs #389 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Self-review (autopilot worker, not an independent review) — verdict: APPROVE
Diff read against
issue-409-hook-harness(1385 lines, tests anddocs/architecture.mdonly).Acceptance criteria of #389
react-i18nextmock returns a bare key: the 27 files go throughi18nMock; the only faketismarkedT(src/test/i18nMock.ts).tremoved, exit 1 (table in the PR body, 20 mutations over 19 components, incl. the 3 #423 files and the 4 components ofUpsellCtas.render). One survivor on the first pass (OnboardingChecklistregionaria-label), assertion added, red since.npm testgreen: 118 files / 2026 tests (base 116 / 2009);npm run buildgreen.## Tests (vitest sans jsdom).Checks
not.toContainassertions were rewritten in marked form too, so they stay meaningful (a bare-keynot.toContainwould have become vacuous). Data values that are nottoutput (i18nKeyof price fixtures,i18n_keyof a category node) kept bare.Points for the human reviewer
\bt\s*:\s*\(): the literal substring of the issue flags 9 clean files (useLicenseContext: () =>,get: (target...). A third check refuses areact-i18nextmock that bypassesi18nMock.PriceFetchConsentToggle.test.tsxnow mocksreactwith the #409 harness file-wide (render test needs theloadingslot preset).PriceFetchControl.tsxhard-codes abest-effortbadge withoutt.Generated autonomously by /autopilot run of 2026-10-12
CI lue par l’orchestrateur de l’autopilot (2026-10-12) sur la tête
2bd347b:PR Check — Frontendrun 531 vert.Correction du rapport du worker :
PriceFetchControl.tsxn’affiche aucun badgebest-effortcodé en dur. Les trois occurrences sont des commentaires (L2, L9, L173), et l’avis passe part("balance.priceFetching.bestEffortNotice"). Le commentaire d’en-tête L9 parle encore d’un « badge » qui n’existe plus : commentaire périmé, pas une entorse à l’i18n.Complément de la lecture de la CI (run 531) :
Test Files 118 passed (118),Tests 2026 passed (2026).Revue
/pr-review— PR #435 (#389), tête2bd347b, contreissue-409-hook-harness(79ba1ed)Verdict : APPROVE
Le module
src/test/i18nMock.tsfait ce que l'issue demande : un seulmarkedT, une fabrique aveclanguage(valeur ou fonction) etfixture, adoptée par les 27 fichiers qui moquaientreact-i18next(inventaire revérifié : 27 fichiers portenti18nMock, aucunvi.mock("react-i18next"sans lui). La dérive 24 → 27 (trois rendus de #423) est justifiée. L'ordre des commits suit la planification, et la garde ne passe au vert qu'au dernier commit.Ce que j'ai exécuté (worktree détaché hors dépôt,
npm cipropre)npm run buildvert ;npx vitest run: 118 fichiers, 2026 tests verts. CI de la tête :success.markedTsans les options → 47 rouges ;markedTqui rend la clé nue → 128 rouges ;fixtureignorée → 3 rouges ;languagefigé à"fr"(fonction ignorée) → 4 rouges.tretiré » de mon choix, autres que celles du tableau de la PR : 7 rouges (FilterPanelhint,ImportEmptyStatetitres noFolder et noSources,PriceFetchControlbestEffortNotice,PriceFetchConsentTogglelabel,OnboardingChecklisthide et resume). 7 survivent (TransactionTabletransferIcon.ariaLabel et notes.placeholder,LicenseKeyFormactivate et keyPlaceholder,PriceFetchControlconsent.accept,DashboardPagekpiSectionTitle ;common.nextn'existe pas dansWizardNavigation). Ça reste conforme au critère, qui demande au moins un rouge par composant, et non par appel àt. Je le note pour qu'on ne lise pas la garde comme « touttoublié rougit ».useTranslation() { … }en méthode courte, sansi18nMock→ rouge (3ᵉ contrôle) ;i18nMock→ vert ;const t = (k: string) => k→ vert ;{ t: vi.fn((k) => k) }→ vert.git checkout, etgit statusest vide après chaque passe.Suggestions (non bloquantes)
treste dans le dépôt :src/utils/renderCategoryLabel.test.ts:13,const t = (key, options) => known[key] ?? options?.defaultValue ?? key, passé en paramètre àrenderCategoryLabel. Il ne cache pas detoublié aujourd'hui, puisque les assertions lisent des textes deknown. Mais le critère « un seul fauxtdans le dépôt » et la phrase dedocs/architecture.md(« Le seul fauxtdu dépôt estmarkedT») sont donc inexacts, et la garde ne le voit pas, faute de motift = (. Deux sorties : migrer ce fichier versmarkedT+fixture(le replidefaultValuedemande un petit habillage), ou restreindre la phrase de la doc aux mocks dereact-i18next.\bt\s*=\s*\(et\bt\s*:\s*vi\.fn\(passent tous deux. Le 3ᵉ contrôle (source.includes("i18nMock")) est satisfait par n'importe quelle mention, un commentaire compris : unvi.mock("react-i18next"qui ne contient pas l'appeli18nMock(serait plus strict. Le cas courant (un mock local copié-collé) est bien pris, d'où le caractère non bloquant.PriceFetchConsentToggle — displayed textsremplit les cases du harnais par ordre d'appel (slots[0..2]) : unuseStateajouté en tête du composant décale tout en silence. Le commentaire demarkup()le dit, c'est la limite connue du harnais de #409.best-effortcodé en dur dansPriceFetchControl.tsxmérite son issue.Fermeture
Le corps de la PR ne porte aucun mot-clé de fermeture, mais le commit de tête contient
Resolves #389. Un merge ff de la pile (/merge-stack) ferme donc #389. Pour l'instant, la timeline n'affiche aucuncloses(ref_action: none).Actions faites
status:reviewàstatus:approved.git worktree remove) en fin de revue.Suivis de cette revue appliqués en tête de pile, PR #436, commit
ac3a981(détail dans le commentaire 23535 de la PR #436, non relu).Correction de l’orchestrateur. Mon commentaire 23450 affirmait que
PriceFetchControl.tsxn’avait pas de badgebest-effortcodé en dur. C’était faux : le badge existe aux lignes 201-206 (<span>dont le texte est le littéralbest-effort, sans passer part). Mongrepétait tronqué par unhead -3. Le worker et la suggestion 4 de la revue avaient raison, et le commentaire d’en-tête L9 n’est pas périmé.Corrigé en tête de pile, sur la PR #436, par le commit
c17bf5d:balance.priceFetching.bestEffortBadge(« non garanti » en FR, « best-effort » en EN) ;