93 lines
2.9 KiB
JavaScript
93 lines
2.9 KiB
JavaScript
import { describe, it, expect, beforeAll } from "vitest";
|
|
import { generateDungeon } from "../../src/dungeonGenerator.js";
|
|
import { generatePDF } from "../../src/generatePDF.js";
|
|
import fs from "fs/promises";
|
|
import path from "path";
|
|
|
|
const hasOllama = !!process.env.OLLAMA_API_URL;
|
|
|
|
describe.skipIf(!hasOllama)("Dungeon generation (Ollama)", () => {
|
|
let dungeonData;
|
|
|
|
beforeAll(async () => {
|
|
dungeonData = await generateDungeon();
|
|
}, 120000);
|
|
|
|
it("generates dungeon data", () => {
|
|
expect(dungeonData).toBeDefined();
|
|
});
|
|
|
|
it("title is 2-4 words, no colons", () => {
|
|
expect(dungeonData.title).toBeTruthy();
|
|
const words = dungeonData.title.split(/\s+/);
|
|
expect(words.length).toBeGreaterThanOrEqual(2);
|
|
expect(words.length).toBeLessThanOrEqual(4);
|
|
expect(dungeonData.title).not.toContain(":");
|
|
});
|
|
|
|
it("flavor text is ≤60 words", () => {
|
|
expect(dungeonData.flavor).toBeTruthy();
|
|
const words = dungeonData.flavor.split(/\s+/);
|
|
expect(words.length).toBeLessThanOrEqual(60);
|
|
});
|
|
|
|
it("hooks have no title prefixes", () => {
|
|
expect(dungeonData.hooksRumors).toBeDefined();
|
|
dungeonData.hooksRumors.forEach((hook) => {
|
|
expect(hook).not.toMatch(/^[^:]+:\s/);
|
|
});
|
|
});
|
|
|
|
it("has exactly 6 random events", () => {
|
|
expect(dungeonData.randomEvents).toBeDefined();
|
|
expect(dungeonData.randomEvents.length).toBe(6);
|
|
});
|
|
|
|
it("encounter details do not start with encounter name", () => {
|
|
expect(dungeonData.encounters).toBeDefined();
|
|
dungeonData.encounters.forEach((encounter) => {
|
|
if (encounter.details) {
|
|
const detailsLower = encounter.details.toLowerCase();
|
|
const nameLower = encounter.name.toLowerCase();
|
|
expect(detailsLower.startsWith(nameLower)).toBe(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
it("treasure descriptions do not start with 'description'", () => {
|
|
expect(dungeonData.treasure).toBeDefined();
|
|
dungeonData.treasure.forEach((item) => {
|
|
if (typeof item === "object" && item.description) {
|
|
expect(item.description.toLowerCase().startsWith("description")).toBe(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
it("NPC traits do not start with 'description'", () => {
|
|
expect(dungeonData.npcs).toBeDefined();
|
|
dungeonData.npcs.forEach((npc) => {
|
|
if (npc.trait) {
|
|
expect(npc.trait.toLowerCase().startsWith("description")).toBe(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
it("PDF fits on one page", async () => {
|
|
const testPdfPath = path.join(process.cwd(), "test-output.pdf");
|
|
try {
|
|
await generatePDF(dungeonData, testPdfPath);
|
|
const pdfBuffer = await fs.readFile(testPdfPath);
|
|
const pdfText = pdfBuffer.toString("binary");
|
|
const pageCount = (pdfText.match(/\/Type\s*\/Page[^s]/g) || []).length;
|
|
const expectedPages = dungeonData.map ? 2 : 1;
|
|
expect(pageCount).toBeLessThanOrEqual(expectedPages);
|
|
} finally {
|
|
try {
|
|
await fs.unlink(testPdfPath);
|
|
} catch {
|
|
// Ignore cleanup errors
|
|
}
|
|
}
|
|
}, 60000);
|
|
});
|