add testing suite

This commit is contained in:
2026-02-21 22:19:21 -05:00
parent 07128c3529
commit 83eee20b2c
25 changed files with 5317 additions and 1628 deletions

View File

@@ -0,0 +1,227 @@
import { describe, it, expect } from "vitest";
import {
escapeHtml,
truncateText,
parseEventForDisplay,
dungeonTemplate,
} from "../../src/dungeonTemplate.js";
describe("escapeHtml", () => {
it("returns empty string for empty input", () => {
expect(escapeHtml("")).toBe("");
});
it("returns empty string for null/undefined-like", () => {
expect(escapeHtml(null)).toBe("");
expect(escapeHtml(undefined)).toBe("");
});
it("escapes & < > \" '", () => {
expect(escapeHtml("&")).toBe("&amp;");
expect(escapeHtml("<")).toBe("&lt;");
expect(escapeHtml(">")).toBe("&gt;");
expect(escapeHtml('"')).toBe("&quot;");
expect(escapeHtml("'")).toBe("&#039;");
expect(escapeHtml('<script>&"\'</script>')).toBe(
"&lt;script&gt;&amp;&quot;&#039;&lt;/script&gt;"
);
});
it("leaves normal text unchanged", () => {
expect(escapeHtml("Hello World")).toBe("Hello World");
});
});
describe("truncateText", () => {
it("returns empty for empty input", () => {
expect(truncateText("", 1, 100)).toBe("");
});
it("returns text when within sentence and char limits", () => {
const one = "One sentence.";
expect(truncateText(one, 1, 100)).toBe(one);
});
it("truncates to maxSentences", () => {
const three = "First. Second. Third.";
expect(truncateText(three, 1, 500)).toBe("First.");
expect(truncateText(three, 2, 500)).toContain("First.");
expect(truncateText(three, 2, 500)).toContain("Second.");
});
it("truncates by maxChars and ends at sentence boundary when possible", () => {
const long = "A short bit. Then a much longer sentence that goes past the limit we set.";
const out = truncateText(long, 99, 30);
expect(out.length).toBeLessThanOrEqual(33);
expect(out === "A short bit." || out.endsWith("...")).toBe(true);
});
it("appends ... when no sentence boundary near end", () => {
const noPeriod = "No period here and more text";
expect(truncateText(noPeriod, 1, 15)).toMatch(/\.\.\.$/);
});
});
describe("parseEventForDisplay", () => {
it("returns object name and description when given object", () => {
const event = { name: "Event A", description: "Something happened." };
const got = parseEventForDisplay(event, 0);
expect(got.name).toBe("Event A");
expect(got.description).toContain("Something");
});
it('parses "Name: Description" string', () => {
const got = parseEventForDisplay("Fire: The room catches fire.", 0);
expect(got.name).toBe("Fire");
expect(got.description).toContain("catches fire");
});
it("splits string without colon into first two words as name, rest as description", () => {
const got = parseEventForDisplay("One Two Three Four", 0);
expect(got.name).toBe("One Two");
expect(got.description).toBe("Three Four");
});
it("uses fallback Event N and full string for short string", () => {
const got = parseEventForDisplay("Hi", 2);
expect(got.name).toBe("Event 3");
expect(got.description).toBe("Hi");
});
it("handles non-string non-object with index", () => {
const got = parseEventForDisplay(null, 1);
expect(got.name).toBe("Event 2");
expect(got.description).toBe("");
});
});
describe("dungeonTemplate", () => {
it("produces HTML with title and main sections for minimal data", () => {
const data = {
title: "Test Dungeon",
flavor: "A dark place.",
hooksRumors: ["Hook one.", "Hook two."],
rooms: [{ name: "Room 1", description: "A room." }],
encounters: [{ name: "Encounter 1", details: "Hall: Something happens." }],
npcs: [{ name: "NPC 1", trait: "A guard." }],
treasure: [{ name: "Gold", description: "Shiny." }],
randomEvents: [{ name: "Event 1", description: "Something." }],
plotResolutions: ["Resolution one."],
};
const html = dungeonTemplate(data);
expect(html).toContain("Test Dungeon");
expect(html).toContain("A dark place.");
expect(html).toContain("Room 1");
expect(html).toContain("Encounter 1");
expect(html).toContain("NPC 1");
expect(html).toContain("Gold");
expect(html).toContain("Event 1");
expect(html).toContain("Resolution one.");
expect(html).toContain("<!DOCTYPE html>");
});
it("includes map page when data.map is data URL", () => {
const data = {
title: "With Map",
flavor: "Flavor.",
map: "data:image/png;base64,abc123",
hooksRumors: ["H1"],
rooms: [],
encounters: [],
npcs: [],
treasure: [],
randomEvents: [],
plotResolutions: [],
};
const html = dungeonTemplate(data);
expect(html).toContain("map-page");
expect(html).toContain("data:image/png;base64,abc123");
});
it("omits flavor paragraph when flavor is empty", () => {
const data = {
title: "No Flavor",
flavor: "",
hooksRumors: ["H1"],
rooms: [],
encounters: [],
npcs: [],
treasure: [],
randomEvents: [],
plotResolutions: [],
};
const html = dungeonTemplate(data);
expect(html).toContain("No Flavor");
expect(html).not.toMatch(/<p class="flavor">/);
});
it("renders treasure as string Name — Desc", () => {
const data = {
title: "T",
flavor: "F",
hooksRumors: [],
rooms: [],
encounters: [],
npcs: [],
treasure: ["Gold — Shiny coins."],
randomEvents: [],
plotResolutions: [],
};
const html = dungeonTemplate(data);
expect(html).toContain("Gold");
expect(html).toContain("Shiny coins");
});
it("renders NPC as string Name: Trait", () => {
const data = {
title: "T",
flavor: "F",
hooksRumors: [],
rooms: [],
encounters: [],
npcs: ["Guard: A stern guard."],
treasure: [],
randomEvents: [],
plotResolutions: [],
};
const html = dungeonTemplate(data);
expect(html).toContain("Guard");
expect(html).toContain("stern guard");
});
it("strips location prefix from encounter details when it looks like a location name", () => {
const data = {
title: "T",
flavor: "F",
hooksRumors: [],
rooms: [{ name: "Grand Hall", description: "Big." }],
encounters: [
{ name: "E1", details: "Grand Hall: The fight happens here in the hall." },
],
npcs: [],
treasure: [],
randomEvents: [],
plotResolutions: [],
};
const html = dungeonTemplate(data);
expect(html).toContain("The fight happens here");
});
it("renders encounter details without name when details start with encounter name", () => {
const data = {
title: "T",
flavor: "F",
hooksRumors: [],
rooms: [],
encounters: [
{ name: "Goblin Attack", details: "Goblin Attack: They strike." },
],
npcs: [],
treasure: [],
randomEvents: [],
plotResolutions: [],
};
const html = dungeonTemplate(data);
expect(html).toContain("They strike");
});
});