Files
ihasmail/web/src/store/__tests__/lang-attribute.test.ts
T

71 lines
2.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { applyLang, DEFAULT_SETTINGS } from "@/store/settings";
import { UI_LANGUAGES } from "@/lib/languages";
/**
* `<html lang>` has to be right *before first paint*, not after mount.
*
* Chrome decides whether to offer a translation from the language it detects
* against the language the page declares. A `lang` patched in from a
* `useEffect` leaves a window where the two disagree, and that window is
* enough to raise the prompt on a page that was already in the reader's
* language — after which accepting it rewrites the DOM under React.
*
* Two halves, and they are different claims: the served HTML already says so,
* and the stored preference is applied without waiting for a render.
*/
describe("the served HTML", () => {
it("declares a language in the markup itself, not from script", () => {
// jsdom rewrites import.meta.url to an http URL, so resolve from the
// Vite root instead of relative to this file.
const html = readFileSync(resolve(process.cwd(), "index.html"), "utf8");
expect(html).toMatch(/<html[^>]*\slang="en"/);
});
});
describe("applyLang", () => {
beforeEach(() => {
document.documentElement.removeAttribute("lang");
});
it("puts the resolved language on the document", () => {
applyLang({ ...DEFAULT_SETTINGS, uiLanguage: "en" });
expect(document.documentElement.lang).toBe("en");
});
it("serves every language whose catalog is shipped", () => {
for (const l of UI_LANGUAGES) {
applyLang({ ...DEFAULT_SETTINGS, uiLanguage: l.tag });
expect(document.documentElement.lang).toBe(l.tag);
}
});
it("falls back to English rather than claiming a language it cannot render", () => {
/*
* The tag is derived, not written down. Naming a real language here means
* the test breaks the day that language ships -- which it did, twice, for
* German and then French, each time reporting a failure that was really
* the test being out of date.
*/
const unshipped = ["cy", "is", "mt", "eu"].find((tag) => !UI_LANGUAGES.some((l) => l.tag === tag));
expect(unshipped).toBeDefined();
applyLang({ ...DEFAULT_SETTINGS, uiLanguage: unshipped! });
expect(document.documentElement.lang).toBe("en");
});
it("is applied from the module the app imports before it renders", async () => {
/*
* main.tsx imports App, which reaches this store, before it calls
* createRoot().render(). Re-importing runs the module's own side effects
* against a document that has just had the attribute stripped, which is
* the closest a test runner gets to "was it set before the first paint".
*/
document.documentElement.removeAttribute("lang");
vi.resetModules();
await import("@/store/settings");
expect(document.documentElement.lang).toBe("en");
});
});