import { test } from "node:test";
import assert from "node:assert/strict";
import { createLocalizer, createLanguageController, overlayPack, fetchPack } from "./runtime.mjs";
const pack = locale => ({ version: 1, locale, ruleVersion: "cldr-46.1", pluralRules: { en: { one: "i = 1 and v = 0" }, fr: { one: "i = 0,1" }, de: { one: "i = 1 and v = 0" } }, config: { sourceLocale: "en", defaultLocale: "en", locales: ["en", "fr", "de"] }, documents: [
{ version: 1, locale: "en", namespace: "ui", messages: { title: { value: { text: "English" } }, greet: { params: { name: "string" }, value: { text: "Hello {name}" } } } },
... (locale === "en" ? [] : [{ version: 1, locale, namespace: "ui", messages: { title: { value: { text: locale } } } }])
] });
test("detached resources, explicit overrides, text arguments and fallback", () => {
const data = pack("fr"), localizer = createLocalizer(data);
data.documents[0].messages.greet.value.text = "changed";
assert.equal(localizer.t("ui", "greet", { name: "{name}" }), "Hello {name}");
assert.equal(localizer.render("ui", "greet", { name: "x" }).locale, "en");
const original = pack("en");
const overridden = overlayPack(original, [{ version: 1, namespace: "ui", locale: "en", messages: { title: { value: { text: "Custom" } } } }]);
assert.equal(createLocalizer(overridden).t("ui", "title"), "Custom");
assert.equal(createLocalizer(original).t("ui", "title"), "English");
assert.throws(() => overlayPack(original, [{ version: 1, namespace: "ui", locale: "en", messages: { unknown: { value: {} } } }]));
});
test("latest selection wins and failures preserve committed state", async () => {
const pending = new Map();
const controller = createLanguageController(pack("en"), locale => new Promise((resolve, reject) => pending.set(locale, { resolve, reject })));
const french = controller.setLanguage("fr"), german = controller.setLanguage("de");
pending.get("de").resolve(pack("de")); assert.equal(await german, true);
pending.get("fr").resolve(pack("fr")); assert.equal(await french, false);
assert.equal(controller.current.locale, "de");
const failed = controller.setLanguage("fr"); pending.get("fr").reject(Error("offline"));
await assert.rejects(failed); assert.equal(controller.current.locale, "de");
const stale = controller.setLanguage("fr"); await controller.setLanguage("de");
pending.get("fr").reject(Error("stale failure")); assert.equal(await stale, false);
assert.equal(controller.current.t("ui", "title"), "de");
});
test("bad packs cannot commit or change enabled languages/schemas", async () => {
const controller = createLanguageController(pack("en"), async () => { const data = pack("fr"); delete data.documents[0].messages.greet; return data; });
await assert.rejects(controller.setLanguage("fr")); assert.equal(controller.current.locale, "en");
await assert.rejects(controller.setLanguage("zz"));
const cycle = pack("en"); cycle.config.fallbacks = { en: ["fr"], fr: ["en"] }; assert.throws(() => createLocalizer(cycle));
const duplicate = pack("en"); duplicate.documents.push(duplicate.documents[0]); assert.throws(() => createLocalizer(duplicate));
});
test("bounded fetching and validation before switching", async () => {
const data = await fetchPack("/en.json", { fetch: async () => new Response(JSON.stringify(pack("en"))) });
assert.equal(createLocalizer(data).locale, "en");
await assert.rejects(fetchPack("/bad", { fetch: async () => new Response("bad", { status: 500 }) }));
await assert.rejects(fetchPack("/bad", { fetch: async () => new Response("bad") }));
await assert.rejects(fetchPack("/huge", { fetch: async () => new Response(new Uint8Array(4194305)) }));
});