import { test } from "node:test";
import assert from "node:assert/strict";
import {
HEADER_SOCIAL_LINKS_MAX,
headerSocialLinks,
safeSocialSvg,
scopeSvgIds,
sizeSocialSvg,
socialLinksForSave,
} from "./socialLinks";
import { normalizeSocialSvg, parseSocialLinks, type SocialLink } from "./settingsSchema";
const SVG = ``;
const link = (label: string, featured?: boolean): SocialLink => ({
label,
url: `https://${label.toLowerCase()}.example`,
svg: SVG,
...(featured ? { featured: true } : {}),
});
const labels = (links: SocialLink[]) => links.map((l) => l.label);
test("the header bound is four", () => {
assert.equal(HEADER_SOCIAL_LINKS_MAX, 4);
});
test("wide: four or fewer links → all of them, in order, marked or not", () => {
assert.deepEqual(headerSocialLinks([], "wide"), []);
assert.deepEqual(labels(headerSocialLinks([link("A")], "wide")), ["A"]);
assert.deepEqual(
labels(headerSocialLinks([link("A"), link("B", true), link("C"), link("D")], "wide")),
["A", "B", "C", "D"],
);
});
test("wide: six links, none marked → the last four", () => {
const six = ["A", "B", "C", "D", "E", "F"].map((l) => link(l));
assert.deepEqual(labels(headerSocialLinks(six, "wide")), ["C", "D", "E", "F"]);
});
test("wide: six links, some marked → the marked kept first, the last of the rest after, in configured order", () => {
const six = [link("A", true), link("B"), link("C"), link("D", true), link("E"), link("F")];
assert.deepEqual(labels(headerSocialLinks(six, "wide")), ["A", "D", "E", "F"]);
const one = [link("A", true), link("B"), link("C"), link("D"), link("E"), link("F")];
assert.deepEqual(labels(headerSocialLinks(one, "wide")), ["A", "D", "E", "F"]);
});
test("wide: more than four marked → the last four marked", () => {
const six = ["A", "B", "C", "D", "E", "F"].map((l) => link(l, l !== "F"));
assert.deepEqual(labels(headerSocialLinks(six, "wide")), ["B", "C", "D", "E"]);
});
test("narrow: none marked → none; the name wins and the footer has them all", () => {
assert.deepEqual(headerSocialLinks([], "narrow"), []);
assert.deepEqual(headerSocialLinks([link("A"), link("B")], "narrow"), []);
const six = ["A", "B", "C", "D", "E", "F"].map((l) => link(l));
assert.deepEqual(headerSocialLinks(six, "narrow"), []);
});
test("narrow: the marked ones only, in order; more than four marked → the last four", () => {
assert.deepEqual(labels(headerSocialLinks([link("A"), link("B"), link("C", true)], "narrow")), ["C"]);
const six = [link("A"), link("B", true), link("C"), link("D", true), link("E"), link("F")];
assert.deepEqual(labels(headerSocialLinks(six, "narrow")), ["B", "D"]);
const many = ["A", "B", "C", "D", "E", "F"].map((l) => link(l, l !== "F"));
assert.deepEqual(labels(headerSocialLinks(many, "narrow")), ["B", "C", "D", "E"]);
});
test("header: the input is not changed", () => {
const six = ["A", "B", "C", "D", "E", "F"].map((l) => link(l, l === "B"));
headerSocialLinks(six, "wide");
headerSocialLinks(six, "narrow");
assert.equal(six.length, 6);
assert.deepEqual(labels(six), ["A", "B", "C", "D", "E", "F"]);
});
test("parseSocialLinks: featured is kept only when exactly true", () => {
const parsed = parseSocialLinks([
{ label: "A", url: "https://a.example", svg: SVG, featured: true },
{ label: "B", url: "https://b.example", svg: SVG, featured: false },
{ label: "C", url: "https://c.example", svg: SVG, featured: "yes" },
{ label: "D", url: "https://d.example", svg: SVG },
]);
assert.deepEqual(parsed[0], { label: "A", url: "https://a.example", svg: SVG, featured: true });
for (const l of parsed.slice(1)) assert.ok(!("featured" in l), l.label);
// An old file — no link marked — parses exactly as it did before the key.
const old = [{ label: "A", url: "https://a.example", svg: SVG }];
assert.deepEqual(parseSocialLinks(old), old);
assert.deepEqual(JSON.parse(JSON.stringify(parseSocialLinks(old))), old);
});
test("sizeSocialSvg: an intrinsic size on an unsized root, never a second one", () => {
assert.ok(sizeSocialSvg(SVG).startsWith('`;
assert.equal(sizeSocialSvg(sized), sized);
assert.ok(sizeSocialSvg(SVG, 36).startsWith('`;
test("scopeSvgIds: every id and every reference to one gains the scope", () => {
const out = scopeSvgIds(REFS, "s1-0");
for (const id of ["root", "g", "g2", "m"]) assert.ok(out.includes(`id="s1-0-${id}"`), id);
assert.ok(out.includes(`id='s1-0-c'`));
assert.ok(out.includes(`fill="url(#s1-0-g)"`));
assert.ok(out.includes(`style="fill:url('#s1-0-g2')"`));
assert.ok(out.includes(`clip-path="url(#s1-0-c)"`));
assert.ok(out.includes(`mask="url(#s1-0-m)"`));
assert.ok(out.includes(`xlink:href="#s1-0-g"`));
assert.ok(out.includes(``));
// Not an id of this icon: left alone. So is a colour that looks like one.
assert.ok(out.includes(``));
assert.ok(out.includes(`fill="url(#gx)"`));
assert.ok(out.includes(`stop-color:#D7DF23`));
});
test("scopeSvgIds: two scopes of one icon share no id; no ids → unchanged", () => {
const ids = (svg: string) => [...svg.matchAll(/\sid=["']([^"']+)["']/g)].map((m) => m[1]);
const a = ids(scopeSvgIds(REFS, "a"));
const b = ids(scopeSvgIds(REFS, "b"));
assert.equal(a.length, 5);
assert.ok(a.every((id) => !b.includes(id)));
assert.equal(scopeSvgIds(SVG, "a"), SVG);
});
test("scopeSvgIds: entity-quoted and case-varied references are scoped with their id", () => {
const svg =
``;
const out = scopeSvgIds(svg, "s");
assert.ok(out.includes(`ID="s-g"`));
assert.ok(out.includes(`mask="url("#s-g")"`));
assert.ok(out.includes(`fill="url('#s-g')"`));
assert.ok(out.includes(`HREF="#s-g"`));
});
test("scopeSvgIds: an id that is not a plain name is left alone, so no comment can close early", () => {
for (const id of ["->", "-!>"]) {
const svg = ``;
assert.equal(scopeSvgIds(svg, "s"), svg, id);
}
});
// Every icon the checker accepts, scoped as a page renders it, still passes the
// checker: the render never makes markup the save would have refused.
test("a scoped and sized icon still passes the checker", () => {
for (const raw of [
``,
``,
]) {
const stored = normalizeSocialSvg(raw);
assert.ok(stored, raw.slice(0, 60));
const rendered = sizeSocialSvg(scopeSvgIds(stored, "sl_S_1_-0"));
assert.equal(normalizeSocialSvg(rendered)?.includes("sl_S_1_-0-"), true);
}
});
test("safeSocialSvg: a stored icon that fails the check is not rendered", () => {
assert.equal(safeSocialSvg(SVG), SVG);
for (const bad of [
``,
``,
``,
"not an svg",
42,
undefined,
]) {
assert.equal(safeSocialSvg(bad), null, String(bad).slice(0, 40));
}
});
test("socialLinksForSave: an unchanged stored icon is kept as it is; a new or edited one is checked", () => {
const refusedByNow = ``;
const stored: SocialLink[] = [{ label: "Old", url: "https://old.example", svg: refusedByNow }];
// Unchanged (a label or `featured` may change): kept byte-identical.
const same = socialLinksForSave([{ ...stored[0], label: "Renamed", featured: true }], stored);
assert.deepEqual(same, { links: [{ ...stored[0], label: "Renamed", featured: true }] });
// Edited to something refused: the label and the reason, never the markup.
const bad = socialLinksForSave([{ ...stored[0], svg: `` }], stored);
assert.ok("refused" in bad && bad.refused.label === "Old" && /event handler/.test(bad.refused.problem));
// New and good: normalized.
const fresh = socialLinksForSave([{ label: "New", url: "https://new.example", svg: `` }], stored);
assert.ok("links" in fresh && fresh.links[0].svg.startsWith('