test(i18n): one marked fake t for the suite, and a guard (#389) #435

Merged
maximus merged 4 commits from issue-389-i18n-mock into main 2026-10-14 00:28:40 +00:00
12 changed files with 15 additions and 71 deletions
Showing only changes of commit 063f6c3d7d - Show all commits

View file

@ -10,9 +10,7 @@ import { renderToStaticMarkup } from "react-dom/server";
import type { BudgetYearRow } from "../../shared/types";
import type { CollapseAccessors } from "../../utils/collapsibleRows";
vi.mock("react-i18next", () => ({
useTranslation: () => ({ t: (k: string) => `t(${k})`, i18n: { language: "fr" } }),
}));
vi.mock("react-i18next", async () => (await import("../../test/i18nMock")).i18nMock());
const harness = vi.hoisted(() => ({
/** Keys of the open groups (`p:<category id>`): the grid opens collapsed. */

View file

@ -16,9 +16,7 @@ const ctx = vi.hoisted(() => ({
/** Keys of the open groups (`p:<category id>`): the grid opens collapsed. */
expanded: new Set<string>(),
}));
vi.mock("react-i18next", () => ({
useTranslation: () => ({ t: (k: string) => `t(${k})`, i18n: { language: "fr" } }),
}));
vi.mock("react-i18next", async () => (await import("../../test/i18nMock")).i18nMock());
vi.mock("../../hooks/useCollapsibleGroups", async () => {
const { collapsibleKeys, isCollapsedFor, visibleRows } = await import("../../utils/collapsibleRows");
return {

View file

@ -7,13 +7,7 @@ import type { ReactElement, ReactNode } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { MemoryRouter } from "react-router-dom";
vi.mock("react-i18next", () => ({
useTranslation: () => ({
t: (k: string, o?: Record<string, unknown>) =>
o ? `t(${k}[${Object.entries(o).map(([a, b]) => `${a}=${String(b)}`).join(",")}])` : `t(${k})`,
i18n: { language: "fr" },
}),
}));
vi.mock("react-i18next", async () => (await import("../../test/i18nMock")).i18nMock());
import DashboardEmptyState from "./DashboardEmptyState";
import OnboardingChecklist from "./OnboardingChecklist";

View file

@ -13,13 +13,7 @@ import { MemoryRouter, Route, Routes } from "react-router-dom";
// plugins) and useEntitlement (→ LicenseContext): stub both.
vi.mock("../../services/db", () => ({ getDb: vi.fn(), withTransaction: vi.fn() }));
vi.mock("../../contexts/LicenseContext", () => ({ useLicenseContext: vi.fn() }));
vi.mock("react-i18next", () => ({
useTranslation: () => ({
t: (k: string, o?: Record<string, unknown>) =>
o ? `t(${k}[${Object.entries(o).map(([a, b]) => `${a}=${String(b)}`).join(",")}])` : `t(${k})`,
i18n: { language: "fr" },
}),
}));
vi.mock("react-i18next", async () => (await import("../../test/i18nMock")).i18nMock());
vi.mock("./Sidebar", () => ({ default: () => <nav>sidebar</nav> }));
let onboarding: OnboardingValue;

View file

@ -7,15 +7,9 @@ import { isValidElement, type ReactElement, type ReactNode } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { Link, MemoryRouter } from "react-router-dom";
// The fake `t` marks what it translates, `t(key[options])`: a string that
// The shared fake `t` (src/test/i18nMock.ts) marks what it translates, `t(key[options])`: a string that
// skipped `t` would show unmarked.
vi.mock("react-i18next", () => ({
useTranslation: () => ({
t: (k: string, o?: Record<string, unknown>) =>
o ? `t(${k}[${Object.entries(o).map(([a, b]) => `${a}=${String(b)}`).join(",")}])` : `t(${k})`,
i18n: { language: "fr" },
}),
}));
vi.mock("react-i18next", async () => (await import("../../test/i18nMock")).i18nMock());
import OnboardingBanner, {
GETTING_STARTED_GUIDE,

View file

@ -7,15 +7,9 @@ import { createRef, isValidElement, type ReactElement, type ReactNode } from "re
import { renderToStaticMarkup } from "react-dom/server";
import { Link, MemoryRouter } from "react-router-dom";
// The fake `t` marks what it translates, `t(key[options])`: a string that
// The shared fake `t` (src/test/i18nMock.ts) marks what it translates, `t(key[options])`: a string that
// skipped `t` would show unmarked.
vi.mock("react-i18next", () => ({
useTranslation: () => ({
t: (k: string, o?: Record<string, unknown>) =>
o ? `t(${k}[${Object.entries(o).map(([a, b]) => `${a}=${String(b)}`).join(",")}])` : `t(${k})`,
i18n: { language: "fr" },
}),
}));
vi.mock("react-i18next", async () => (await import("../../test/i18nMock")).i18nMock());
import StepCard, { type StepCardProps } from "./StepCard";
import { GETTING_STARTED_GUIDE } from "./OnboardingBanner";

View file

@ -10,16 +10,10 @@ vi.mock("@tauri-apps/api/core", () => ({ invoke: vi.fn() }));
vi.mock("@tauri-apps/plugin-opener", () => ({ openUrl: vi.fn(() => Promise.resolve()) }));
let language = "fr";
// The fake `t` marks what it translates, `t(key[options])`: the assertions written in that form
// The shared fake `t` (src/test/i18nMock.ts) marks what it translates, `t(key[options])`: the assertions written in that form
// fail on a string that skipped `t` (#387 review). Substring assertions on a bare key still read
// the key inside the mark.
vi.mock("react-i18next", () => ({
useTranslation: () => ({
t: (k: string, o?: Record<string, unknown>) =>
o ? `t(${k}[${Object.entries(o).map(([a, b]) => `${a}=${String(b)}`).join(",")}])` : `t(${k})`,
i18n: { language },
}),
}));
vi.mock("react-i18next", async () => (await import("../../test/i18nMock")).i18nMock({ language: () => language }));
let context: Record<string, unknown>;
vi.mock("../../contexts/LicenseContext", async (importOriginal) => ({

View file

@ -11,13 +11,7 @@ import { MemoryRouter } from "react-router-dom";
// The budget, collapse and preference services pull the database: stub the connection.
vi.mock("../services/db", () => ({ getDb: vi.fn(), withTransaction: vi.fn() }));
vi.mock("react-i18next", () => ({
useTranslation: () => ({
t: (k: string, o?: Record<string, unknown>) =>
o ? `t(${k}[${Object.entries(o).map(([a, b]) => `${a}=${String(b)}`).join(",")}])` : `t(${k})`,
i18n: { language: "fr" },
}),
}));
vi.mock("react-i18next", async () => (await import("../test/i18nMock")).i18nMock());
vi.mock("../components/shared/PageHelp", () => ({ PageHelp: () => null }));
const harness = vi.hoisted(() => ({ budget: null as unknown, onboarding: null as unknown }));

View file

@ -19,9 +19,7 @@ vi.mock("react-router-dom", async (importOriginal) => ({
...(await importOriginal<typeof import("react-router-dom")>()),
useLocation: () => ({ pathname: ctx.pathname }),
}));
vi.mock("react-i18next", () => ({
useTranslation: () => ({ t: (k: string) => `t(${k})`, i18n: { language: "fr" } }),
}));
vi.mock("react-i18next", async () => (await import("../test/i18nMock")).i18nMock());
// The page's modules pull the database: stub the connection.
vi.mock("../services/db", () => ({ getDb: vi.fn(), withTransaction: vi.fn() }));
vi.mock("../contexts/OnboardingContext", () => ({ useOnboarding: () => ctx.onboarding }));

View file

@ -12,13 +12,7 @@ import { MemoryRouter } from "react-router-dom";
// Importing the real wizard module (for its initial state) pulls the import
// services, then the database: stub the connection.
vi.mock("../services/db", () => ({ getDb: vi.fn(), withTransaction: vi.fn() }));
vi.mock("react-i18next", () => ({
useTranslation: () => ({
t: (k: string, o?: Record<string, unknown>) =>
o ? `t(${k}[${Object.entries(o).map(([a, b]) => `${a}=${String(b)}`).join(",")}])` : `t(${k})`,
i18n: { language: "fr" },
}),
}));
vi.mock("react-i18next", async () => (await import("../test/i18nMock")).i18nMock());
const harness = vi.hoisted(() => ({ wizard: null as unknown, onboarding: null as unknown }));

View file

@ -12,13 +12,7 @@ import type { TransactionFilters, TransactionRow } from "../shared/types";
// The page's services pull the database: stub the connection.
vi.mock("../services/db", () => ({ getDb: vi.fn(), withTransaction: vi.fn() }));
vi.mock("react-i18next", () => ({
useTranslation: () => ({
t: (k: string, o?: Record<string, unknown>) =>
o ? `t(${k}[${Object.entries(o).map(([a, b]) => `${a}=${String(b)}`).join(",")}])` : `t(${k})`,
i18n: { language: "fr" },
}),
}));
vi.mock("react-i18next", async () => (await import("../test/i18nMock")).i18nMock());
vi.mock("../components/shared/PageHelp", () => ({ PageHelp: () => null }));
const harness = vi.hoisted(() => ({

View file

@ -11,9 +11,7 @@ import { isValidElement, type ReactElement, type ReactNode } from "react";
vi.mock("react", async (orig) => (await import("../../test/hookHarness")).fakeReact(await orig()));
const ctx = vi.hoisted(() => ({ onboarding: null as unknown }));
vi.mock("react-i18next", () => ({
useTranslation: () => ({ t: (k: string) => `t(${k})`, i18n: { language: "fr" } }),
}));
vi.mock("react-i18next", async () => (await import("../../test/i18nMock")).i18nMock());
vi.mock("../../contexts/OnboardingContext", () => ({
useOnboarding: () => ctx.onboarding,
}));