tests/langPref.test.ts
Rama main · réplica de lectura del repositorio original.
// WHOSE LANGUAGE IS THIS SESSION READING IN. One table, because the two
// per-browser preferences are easy to get wrong in each other's favour and
// the product shipped one of those wrongs: `loadMe()` applied the VISITOR's
// stored choice over `me.language` for every session, admin included. One tap
// on the public site's ع and the owner's editor, sidebar, tabs, status bar
// and command palette were Arabic too — and on an instance whose
// publicLayout is "app" the blog shell never renders, so the EN/ع control
// that set it does not exist anywhere in the product. There was no way back
// from inside the app.
//
// The rule under test is the fix, and it is deliberately a pure function
// (client/langPref.ts) rather than four lines inside loadMe: it is the only
// place either preference is allowed to be consulted, so a table like this
// one can hold it to the two invariants that matter —
//
// 1. A visitor's stored choice NEVER reaches an admin's chrome.
// 2. An admin's stored choice NEVER reaches a visitor's, and therefore
// never touches what is published.
//
// `admin` here is the server's word (`/api/me`), which reports false for an
// admin previewing as a visitor — the row that keeps the preview honest.
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { chromeLang, chromeLangPref, otherLang } from "../client/langPref.ts";
import { CHROME_LANG_ALIASES, chromeLangSwitch } from "../client/chromeLangSwitch.ts";
type Lang = "en" | "ar";
interface Case {
name: string;
admin: boolean;
languageToggle: boolean;
siteLang: Lang;
editor: Lang | null;
visitor: Lang | null;
want: Lang;
}
const CASES: Case[] = [
// ── The regression this file exists for ─────────────────────────────────
{
name: "the owner's editor ignores a visitor choice made in the same browser",
admin: true,
languageToggle: true,
siteLang: "en",
editor: null,
visitor: "ar",
want: "en",
},
{
name: "…and ignores it just as hard when the site itself is Arabic",
admin: true,
languageToggle: true,
siteLang: "ar",
editor: null,
visitor: "en",
want: "ar",
},
// ── The editor preference: the owner's own, and only theirs ─────────────
{
name: "an English editor over an Arabic site",
admin: true,
languageToggle: false,
siteLang: "ar",
editor: "en",
visitor: null,
want: "en",
},
{
name: "an Arabic editor over an English site",
admin: true,
languageToggle: false,
siteLang: "en",
editor: "ar",
visitor: null,
want: "ar",
},
{
name: "null is Follow site — the default, and the way back from a pin",
admin: true,
languageToggle: false,
siteLang: "ar",
editor: null,
visitor: null,
want: "ar",
},
{
name: "a visitor never inherits the admin's editor language",
admin: false,
languageToggle: true,
siteLang: "ar",
editor: "en",
visitor: null,
want: "ar",
},
{
name: "…not even when the visitor has stated the opposite of it",
admin: false,
languageToggle: true,
siteLang: "en",
editor: "en",
visitor: "ar",
want: "ar",
},
// ── The visitor switch, unchanged ───────────────────────────────────────
{
name: "a visitor's stored choice wins while the instance offers the switch",
admin: false,
languageToggle: true,
siteLang: "en",
editor: null,
visitor: "ar",
want: "ar",
},
{
name: "turning the switch off restores the site language for everyone",
admin: false,
languageToggle: false,
siteLang: "en",
editor: null,
visitor: "ar",
want: "en",
},
{
name: "a visitor who never chose reads the site language",
admin: false,
languageToggle: true,
siteLang: "ar",
editor: null,
visitor: null,
want: "ar",
},
// ── Visitor preview: the server says admin:false, and that is the point ──
{
name: "previewing as a visitor shows the VISITOR's language, not the editor's",
admin: false, // an admin under X-Astrolabe-Preview
languageToggle: true,
siteLang: "en",
editor: "en",
visitor: "ar",
want: "ar",
},
{
name: "previewing with no visitor choice shows the site language",
admin: false,
languageToggle: true,
siteLang: "ar",
editor: "en",
visitor: null,
want: "ar",
},
];
describe("chromeLang", () => {
for (const c of CASES) {
it(c.name, () => {
assert.equal(
chromeLang({
admin: c.admin,
languageToggle: c.languageToggle,
siteLang: c.siteLang,
editor: c.editor,
visitor: c.visitor,
}),
c.want,
);
});
}
// The two invariants stated as invariants rather than as sampled rows: over
// every combination of the five inputs, neither preference may be the thing
// that decides the other's session. A future third preference (a per-device
// default, a URL override) has to keep both true or this fails.
it("no visitor preference can move an admin's chrome", () => {
for (const languageToggle of [true, false]) {
for (const siteLang of ["en", "ar"] as const) {
for (const editor of ["en", "ar", null] as const) {
const base = { admin: true, languageToggle, siteLang, editor };
const withVisitor = (visitor: Lang | null) => chromeLang({ ...base, visitor });
assert.equal(withVisitor("en"), withVisitor(null));
assert.equal(withVisitor("ar"), withVisitor(null));
assert.equal(withVisitor(null), editor ?? siteLang);
}
}
}
});
it("no editor preference can move a visitor's chrome", () => {
for (const languageToggle of [true, false]) {
for (const siteLang of ["en", "ar"] as const) {
for (const visitor of ["en", "ar", null] as const) {
const base = { admin: false, languageToggle, siteLang, visitor };
const withEditor = (editor: Lang | null) => chromeLang({ ...base, editor });
assert.equal(withEditor("en"), withEditor(null));
assert.equal(withEditor("ar"), withEditor(null));
assert.equal(withEditor(null), (languageToggle ? visitor : null) ?? siteLang);
}
}
}
});
});
describe("otherLang", () => {
it("is the pair the EN/ع switch offers, in both directions", () => {
assert.equal(otherLang("en"), "ar");
assert.equal(otherLang("ar"), "en");
});
});
// THE WAY BACK (chromeLangSwitch / chromeLangPref): the always-visible
// switch's label and the preference a press writes. The owner's words: "if
// they switch by mistake then ggs they won't know how to switch back" — so
// the label must be readable by the person who CANNOT read the chrome.
describe("the chrome-language switch", () => {
it("shows the OTHER language's own name, never the current one", () => {
assert.equal(chromeLangSwitch("en").glyph, "ع");
assert.equal(chromeLangSwitch("ar").glyph, "EN");
assert.equal(chromeLangSwitch("en").target, "ar");
assert.equal(chromeLangSwitch("ar").target, "en");
});
it("names itself in both languages, the one on screen first", () => {
assert.equal(chromeLangSwitch("en").title, "Switch to Arabic · التبديل إلى العربية");
assert.equal(chromeLangSwitch("ar").title, "التبديل إلى الإنجليزية · Switch to English");
});
it("leads the palette row with the language it switches TO", () => {
assert.equal(chromeLangSwitch("en").row, "التبديل إلى العربية · Switch to Arabic");
assert.equal(chromeLangSwitch("ar").row, "Switch to English · التبديل إلى الإنجليزية");
for (const lang of ["en", "ar"] as const) {
const sw = chromeLangSwitch(lang);
assert.ok(sw.row.startsWith(sw.own), `${lang}: ${sw.row}`);
}
});
it("answers to both languages' names in both scripts", () => {
for (const word of ["English", "Arabic", "العربية", "عربي", "الإنجليزية", "إنجليزي"]) {
assert.ok(CHROME_LANG_ALIASES.includes(word), word);
}
});
it("writes the other language, or follow-the-site when that is the site's own", () => {
assert.equal(chromeLangPref("en", "en"), "ar");
assert.equal(chromeLangPref("ar", "en"), null);
assert.equal(chromeLangPref("ar", "ar"), "en");
assert.equal(chromeLangPref("en", "ar"), null);
});
it("comes home in two presses, to the default, from either site language", () => {
for (const siteLang of ["en", "ar"] as const) {
for (const start of [null, "en", "ar"] as const) {
const chrome = (editor: Lang | null) => chromeLang({ admin: true, languageToggle: false, siteLang, editor, visitor: null });
const first = chromeLangPref(chrome(start), siteLang);
assert.equal(chrome(first), otherLang(chrome(start)), `site ${siteLang}, from ${start}: the first press flips`);
const second = chromeLangPref(chrome(first), siteLang);
assert.equal(chrome(second), chrome(start), `site ${siteLang}, from ${start}: the second press comes back`);
if (chrome(start) === siteLang) assert.equal(second, null, "and lands on follow, not a pin");
}
}
});
});