// i18nMock — the one fake `t` of the test suite (#389). // // A fake `t` that returns the key as is hides a forgotten call to `t`: a // component that displays `license.removeKey.confirmValid` instead of the // translated text passes every test (#387 review, S1). This fake MARKS what it // translates, `t(key)` or `t(key[a=1,b=2])`, so an assertion written in that // form fails on a string that skipped `t`. A substring assertion on a bare key // still reads the key inside the mark. // // USE — one pattern, at the head of the test module, before the imports of // the code under test (the factory of `vi.mock` is hoisted, hence the dynamic // import inside it): // // vi.mock("react-i18next", async () => (await import("../test/i18nMock")).i18nMock()); // // Options of the factory: // - `language`: the `i18n.language` the component reads, "fr" by default. A // function is read at each access, for a test that changes the language // between two renders: `i18nMock({ language: () => language })` with a // `let language` of the test module. // - `fixture`: values `t` returns for some keys, as i18next returns what the // locale declares under them (an object with `returnObjects`, for instance). // Every other key goes through `markedT`. // // A test that calls a function taking `t` as a parameter passes `markedT` // itself. `src/__integration__/i18n-mock-guard.test.ts` refuses any other // fake `t` in a test file. /** The marked translation: `t(key)`, or `t(key[a=1,b=2])` with options. */ export function markedT(key: string, options?: Record): string { const params = options ? `[${Object.entries(options) .map(([name, value]) => `${name}=${String(value)}`) .join(",")}]` : ""; return `t(${key}${params})`; } export interface I18nMockOptions { /** `i18n.language`, "fr" by default; a function is read at each access. */ language?: string | (() => string); /** Values returned as is for these keys, instead of the mark. */ fixture?: Record; } /** The module `react-i18next` for `vi.mock`: `useTranslation` with the marked `t`. */ export function i18nMock({ language = "fr", fixture }: I18nMockOptions = {}) { const t = (key: string, options?: Record): unknown => fixture && Object.prototype.hasOwnProperty.call(fixture, key) ? fixture[key] : markedT(key, options); const i18n = { get language(): string { return typeof language === "function" ? language() : language; }, }; return { useTranslation: () => ({ t, i18n }) }; }