From f5e5897df158bd84fa53835efc758549cee85103 Mon Sep 17 00:00:00 2001 From: le king fu Date: Mon, 12 Oct 2026 19:56:27 -0400 Subject: [PATCH] test(i18n): move the raw fake t files to the marked i18nMock The 15 test files whose fake t returned the bare key (the 12 of the issue, plus the three category render tests added by #423) now mock react-i18next with i18nMock. Assertions naming a displayed text are rewritten in the marked form, t(key[options]), so a text that skipped t fails them. Components without such an assertion get one: PriceFetchControl, PriceFetchConsentToggle (rendered through the hook harness, its loading slot preset), CategoryDetailPanel, SourceList and SourceCard, and the region label of OnboardingChecklist. Each component was proved by a mutation removing one t call: every one turns at least one test red (exit 1). Refs #389 Co-Authored-By: Claude Opus 5.5 --- .../balance/PriceFetchControl.test.tsx | 46 +++++++++++----- .../AddKeywordDialog.render.test.tsx | 12 ++--- .../CategoryDetailPanel.render.test.tsx | 19 ++++--- .../categories/CategoryForm.render.test.tsx | 14 ++--- .../dashboard/OnboardingChecklist.test.tsx | 49 ++++++++--------- .../import/ImportEmptyState.test.tsx | 36 ++++++------- src/components/import/SourceList.test.tsx | 32 ++++++++--- .../import/WizardNavigation.test.tsx | 8 ++- src/components/reports/FilterPanel.test.tsx | 11 ++-- .../settings/DocsSubsections.test.tsx | 19 ++++--- .../settings/LicenseKeyForm.render.test.tsx | 13 ++--- .../settings/PriceFetchConsentToggle.test.tsx | 51 ++++++++++++++++-- .../shared/UpsellCtas.render.test.tsx | 54 +++++++++---------- .../TransactionTable.render.test.tsx | 16 ++---- src/pages/DashboardPage.render.test.tsx | 6 +-- 15 files changed, 209 insertions(+), 177 deletions(-) diff --git a/src/components/balance/PriceFetchControl.test.tsx b/src/components/balance/PriceFetchControl.test.tsx index ad30af2..2090daf 100644 --- a/src/components/balance/PriceFetchControl.test.tsx +++ b/src/components/balance/PriceFetchControl.test.tsx @@ -27,19 +27,8 @@ vi.mock("../../services/userPreferenceService", () => ({ setPreference: vi.fn(), })); -// react-i18next: return the key as-is for tests -vi.mock("react-i18next", () => ({ - useTranslation: vi.fn(() => ({ - t: (key: string, opts?: Record) => { - // Include interpolation values in the returned string for assertions - if (opts) { - return `${key}(${JSON.stringify(opts)})`; - } - return key; - }, - i18n: { language: "fr" }, - })), -})); +// react-i18next: the shared marked `t` (#389), so a text that skipped `t` fails +vi.mock("react-i18next", async () => (await import("../../test/i18nMock")).i18nMock()); // lucide-react: return simple stubs vi.mock("lucide-react", () => ({ @@ -58,7 +47,8 @@ import { getPreference, setPreference, } from "../../services/userPreferenceService"; -import { +import { renderToStaticMarkup } from "react-dom/server"; +import PriceFetchControl, { __resetBestEffortDismissForTests, } from "./PriceFetchControl"; @@ -363,3 +353,31 @@ describe("PriceFetchControl — fetchPrice invocation args", () => { expect(mockSetPreference).not.toHaveBeenCalled(); }); }); + +// --------------------------------------------------------------------------- +// Test: what the control displays (#389) +// --------------------------------------------------------------------------- + +describe("PriceFetchControl — displayed texts", () => { + beforeEach(() => { + __resetBestEffortDismissForTests(); + vi.resetAllMocks(); + setPremium(true); + }); + + it("names the fetch button and the stock best-effort notice through t", () => { + const html = renderToStaticMarkup( + , + ); + expect(html).toContain('aria-label="t(balance.priceFetching.button)"'); + expect(html).toContain(">t(balance.priceFetching.button)<"); + expect(html).toContain(">t(balance.priceFetching.bestEffortNotice)<"); + expect(html).toContain('aria-label="t(common.close)"'); + }); +}); diff --git a/src/components/categories/AddKeywordDialog.render.test.tsx b/src/components/categories/AddKeywordDialog.render.test.tsx index 002351d..e09e897 100644 --- a/src/components/categories/AddKeywordDialog.render.test.tsx +++ b/src/components/categories/AddKeywordDialog.render.test.tsx @@ -4,13 +4,7 @@ import { describe, it, expect, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; -vi.mock("react-i18next", () => ({ - useTranslation: () => ({ - t: (k: string, o?: Record) => - o ? `${k}[${Object.entries(o).map(([a, b]) => `${a}=${String(b)}`).join(",")}]` : k, - i18n: { language: "fr" }, - }), -})); +vi.mock("react-i18next", async () => (await import("../../test/i18nMock")).i18nMock()); vi.mock("./keywordCategory", () => ({ loadKeywordCategories: vi.fn(() => new Promise(() => undefined)), @@ -51,12 +45,12 @@ describe("AddKeywordDialog — category field (#423)", () => { it("starts empty, with the 'no category' placeholder, until the list is in", () => { const [input] = markup(7).match(/]*role="combobox"[^>]*>/g) ?? []; expect(input).toContain('value=""'); - expect(input).toContain('placeholder="transactions.table.noCategory"'); + expect(input).toContain('placeholder="t(transactions.table.noCategory)"'); }); it("keeps the apply button disabled before the list is in", () => { const html = markup(7); - const [apply] = html.match(/