// loadCatalog's observable behaviour over 35 documents, catText across the
// three languages, and the card markup social's presence badges select against.
import test from "node:test";
import assert from "node:assert/strict";
import { boot, h, serialize, vectors } from "./harness.mjs";
const { lib } = await boot();
const v = vectors("catalog");
const ct = vectors("cat-text");
const cards = vectors("cards");
test("loadCatalog replays every recorded document", async () => {
const warns = [];
const realWarn = console.warn;
const results = [];
for (const c of v.script) {
globalThis.fetch = async () => {
if (c.throws) throw new Error("offline");
if (c.status) return { ok: false, status: c.status };
if (c.badJson) return { ok: true, status: 200, json: async () => { throw new Error("bad json"); } };
return { ok: true, status: 200, json: async () => c.doc };
};
const seen = [];
console.warn = (...a) => seen.push(a[0]);
let got;
try {
got = { ok: JSON.parse(JSON.stringify(await lib.loadCatalog())) };
} catch (e) {
got = { threw: String(e.message ?? e) };
}
console.warn = realWarn;
results.push(got);
warns.push(seen);
}
console.warn = realWarn;
v.script.forEach((c, i) => {
// the recorded `fellBackToSnapshot` flag is derived, not an input
const want = { ...v.out[i] };
delete want.fellBackToSnapshot;
assert.deepEqual(results[i], want, `case ${i}: ${c.label}`);
assert.deepEqual(warns[i], v.warnings[i], `case ${i} warnings: ${c.label}`);
});
});
test("an entry with no i18n keeps NO i18n key at all in the copy", () => {
// {...a, i18n: undefined} left no key behind in JSON.stringify; the port
// builds the copy with Object.assign + one unchecked-set of js/undefined, so
// the same must hold — a `null` here would change the serialized catalog.
const i = v.script.findIndex((s) => s.label === "missing-optional-lb");
assert.ok(!("i18n" in v.out[i].ok.apps[0]));
const entry = lib.validate(v.script[i].doc).apps[0];
assert.equal(JSON.stringify(entry), JSON.stringify(v.out[i].ok.apps[0]));
});
test("the entry copy keeps its 11-12 keys in source order (the #js-at-nine trap)", () => {
// catalog entries are wider than eight fields, so a #js/js-obj literal would
// have emitted them in hash order. Compare the serialized key sequence.
const doc = {
v: 1,
apps: [{
id: "a", name: "N", host: "h.example", kind: "game", tagline: "t", description: "d",
tags: ["x"], accent: "rose", glyph: "chat", status: "new",
lb: { g: "g", maxScore: 9 }, glyphSvg: "<svg/>",
i18n: { ro: { tagline: "r" } },
}],
};
const got = lib.validate(doc).apps[0];
assert.deepEqual(Object.keys(got), Object.keys(doc.apps[0]));
assert.equal(Object.keys(got).length, 13);
});
test("catText across en/ro/hu", () => {
for (const lang of ct.script.langs) {
lib.setLang(lang);
const got = ct.script.entries.map((e) => [lib.catText(e, "tagline"), lib.catText(e, "description")]);
assert.deepEqual(got, ct.out[lang], `lang ${lang}`);
}
lib.setLang("en");
});
test("renderCatalog emits the historical card markup, per language", () => {
for (const lang of ["en", "ro", "hu"]) {
lib.setLang(lang);
const root = h("div", { class: "apps" });
lib.renderCatalog(root, cards.script);
assert.equal(serialize(root), cards.out[lang], `cards in ${lang}`);
}
lib.setLang("en");
});