Files
ai-spel-frontend/tests/admin-conversation-trace.test.cjs
Ein Anderssono 29c5abe355
Build and publish frontend / publish (push) Successful in 1m15s
Add visual graph map to admin
2026-10-24 03:45:07 +02:00

112 lines
5.7 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const page = fs.readFileSync(new URL('../index.html', `file://${__filename}`), 'utf8');
test('admin can select a searched account before opening its conversations', () => {
assert.match(page, /Välj konto att följa/);
assert.match(page, /chooserSelect\.value\);/);
assert.match(page, /\/api\/admin\/players\?q=/);
});
test('conversation transcript exposes player and narrator messages as expandable replies', () => {
assert.match(page, /function renderAdminConversations/);
assert.match(page, /payload\.player_message/);
assert.match(page, /payload\.reply \|\| payload\.npc_reply/);
assert.match(page, /aria-expanded/);
assert.match(page, /renderDecisionProvenance\(group, event, detail\)/);
});
test('selected replies reveal AI, Luau and Rust provenance without injecting HTML', () => {
assert.match(page, /function renderDecisionProvenance/);
assert.match(page, /interaction\.prompt_yaml/);
assert.match(page, /interaction\.external_call/);
assert.match(page, /details\.luau_trace/);
assert.match(page, /details\.rust_validation/);
assert.match(page, /element\.textContent = text/);
});
test('account trace can be paged until all stored turns are loaded', () => {
assert.match(page, /Visa äldre samtalsturer/);
assert.match(page, /query AdminPlayerTrace\(\$accountId: String, \$limit: Int!, \$offset: Int!\)/);
assert.match(page, /getAdminTrace\(accountId, trace\.limit \|\| 200, adminTraceOffset\)/);
});
test('admin can switch to a storyteller-only reading view and return to diagnostics', () => {
assert.match(page, /Lättläst konversation/);
assert.match(page, /Visa felsökningsspår/);
assert.match(page, /root\.classList\.toggle\('admin-simple-conversation', adminSimpleConversation\)/);
assert.match(page, /playerLine\.className = 'admin-story-player'/);
assert.match(page, /story\.className = 'admin-story-reply'/);
assert.match(page, /article > :not\(\.admin-story-player\):not\(\.admin-story-reply\)/);
assert.match(page, /article > \.admin-story-player,\s*#admin-content \.admin-simple-conversation article > \.admin-story-reply/);
});
test('admin JSON responses are readable as accessible collapsible trees', () => {
assert.match(page, /function renderJsonTree/);
assert.match(page, /nested\.className = 'json-node'/);
assert.match(page, /const nodeValues = new WeakMap\(\)/);
assert.match(page, /nodeValues\.set\(nested, child\)/);
assert.match(page, /groupSummary/);
assert.match(page, /Lista · \$\{item\.length\}/);
assert.match(page, /Objekt · \$\{Object\.keys\(item\)\.length\} fält/);
assert.match(page, /Öppna alla block/);
assert.match(page, /Fäll ihop nästlade block/);
assert.match(page, /setAttribute\('aria-label', 'Utfällbart JSON-svar'\)/);
assert.match(page, /json-value-string/);
assert.match(page, /json-value-number/);
assert.match(page, /json-value-boolean/);
assert.match(page, /json-value-null/);
assert.match(page, /renderJsonTree\(json, group\)/);
assert.match(page, /rawSummary\.textContent = 'Visa hela spårdata'/);
assert.match(page, /JSON\.stringify\(npc\.profile, null, '\\t'\)/);
assert.doesNotMatch(page, /jsnview|cdn\.jsdelivr/);
});
test('admin JSON text decodes common JSON and YAML escapes while retaining the stored form', () => {
const decoder = page.match(/function decodeEscapedSequences\(text\) \{[\s\S]*?\n \}/);
assert.ok(decoder, 'escape decoder should be present');
assert.match(page, /Visa lagrad text med escape-tecken/);
assert.match(page, /decodeEscapedSequences\(item\)/);
const decode = new Function(`${decoder[0]}; return decodeEscapedSequences;`)();
assert.equal(decode(String.raw`Rad 1\nRad 2`), 'Rad 1\nRad 2');
assert.equal(decode(String.raw`Kolumn 1\tKolumn 2`), 'Kolumn 1\tKolumn 2');
assert.equal(decode(String.raw`citat: \'hej\'`), "citat: 'hej'");
assert.equal(decode(String.raw`\u00E5\x21`), 'å!');
assert.equal(decode(String.raw`\U0001F642`), '🙂');
assert.equal(decode(String.raw`okänt: \q`), String.raw`okänt: \q`);
});
test('fenced JSON in AI replies is parsed for readable formatting', () => {
const parser = page.match(/function parseAiResponseSegments\(response\) \{[\s\S]*?\n \}/);
assert.ok(parser, 'AI response segment parser should be present');
assert.match(page, /renderAdminAiResponse\(card, interaction\.response\)/);
assert.match(page, /JSON-svar · formaterat/);
assert.match(page, /admin-ai-response-code/);
const parseSegments = new Function(`${parser[0]}; return parseAiResponseSegments;`)();
const segments = parseSegments('dialogue-turn · completed: ```json { "intent": "speak", "reply": "Rad 1\\nRad 2" }```');
assert.equal(segments[0].type, 'text');
assert.equal(segments[0].value, 'dialogue-turn · completed: ');
assert.equal(segments[1].type, 'json');
assert.deepEqual(segments[1].value, {intent: 'speak', reply: 'Rad 1\nRad 2'});
});
test('admin world map draws stored nodes and exposes only established routes as links', () => {
assert.match(page, /data-admin-view="world-map">Världskarta/);
assert.match(page, /function loadAdminWorldMap/);
assert.match(page, /request\('\/api\/admin\/world-map'\)/);
assert.match(page, /function renderWorld\(worldId\)/);
assert.match(page, /world-map-edge/);
assert.match(page, /world-map-node-\$\{node\.id\}/);
assert.match(page, /edge\.traversable && !edge\.blocked/);
assert.match(page, /location_connections/);
assert.match(page, /scene_relations/);
assert.match(page, /Noder och beskrivningar/);
assert.match(page, /Välj en nod i kartan eller följ en destinationslänk/);
assert.match(page, /button\.dataset\.adminView === 'world-map'/);
});