|
| 1 | +import i18next, { FALLBACK_LANGUAGE, loadLanguage } from "../setup" |
| 2 | + |
| 3 | +// A dynamic import that rejects is what a 404 or a chunk missing from the |
| 4 | +// deployed manifest looks like at runtime. One namespace is enough: `loadLanguage` |
| 5 | +// registers a language only after every namespace resolves. |
| 6 | +vi.mock("../locales/ja/common.json", () => { |
| 7 | + throw new Error("Failed to fetch dynamically imported module") |
| 8 | +}) |
| 9 | + |
| 10 | +describe("i18n setup locale load failure", () => { |
| 11 | + afterEach(() => { |
| 12 | + vi.restoreAllMocks() |
| 13 | + }) |
| 14 | + |
| 15 | + it("resolves without throwing and keeps the English fallback", async () => { |
| 16 | + const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined) |
| 17 | + |
| 18 | + await expect(loadLanguage("ja")).resolves.toBeUndefined() |
| 19 | + |
| 20 | + expect(warn).toHaveBeenCalledWith("Could not load ja translations:", expect.any(Error)) |
| 21 | + // All-or-nothing per language: the namespaces that did resolve are not |
| 22 | + // registered either, so no half-translated locale can be activated. |
| 23 | + expect(i18next.hasResourceBundle("ja", "common")).toBe(false) |
| 24 | + expect(i18next.hasResourceBundle("ja", "settings")).toBe(false) |
| 25 | + expect(i18next.t("common:errorBoundary.title", { lng: "ja" })).toBe("Something went wrong") |
| 26 | + }) |
| 27 | + |
| 28 | + it("retries the fetch on the next call instead of caching the failure", async () => { |
| 29 | + const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined) |
| 30 | + |
| 31 | + await loadLanguage("ja") |
| 32 | + await loadLanguage("ja") |
| 33 | + |
| 34 | + // `loadedLanguages` is only updated on success and `pendingLanguages` is |
| 35 | + // cleared in `finally`, so a transient failure must not be memoized. |
| 36 | + expect(warn).toHaveBeenCalledTimes(2) |
| 37 | + expect(i18next.language).toBe(FALLBACK_LANGUAGE) |
| 38 | + }) |
| 39 | +}) |
0 commit comments