add testing suite
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
export function extractCanonicalNames(dungeonData) {
|
||||
const names = { npcs: [], rooms: [], factions: [] };
|
||||
if (dungeonData.npcs) {
|
||||
dungeonData.npcs.forEach(npc => { if (npc.name) names.npcs.push(npc.name.trim()); });
|
||||
}
|
||||
if (dungeonData.rooms) {
|
||||
dungeonData.rooms.forEach(room => { if (room.name) names.rooms.push(room.name.trim()); });
|
||||
}
|
||||
if (dungeonData.coreConcepts) {
|
||||
const factionMatch = dungeonData.coreConcepts.match(/Primary Faction[:\s]+([^.]+)/i);
|
||||
if (factionMatch) names.factions.push(factionMatch[1].trim());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
export function validateContentCompleteness(dungeonData) {
|
||||
const issues = [];
|
||||
const checks = [
|
||||
['title', 0, 'Missing title'],
|
||||
['flavor', 20, 'Flavor text too short'],
|
||||
['hooksRumors', 4, 'Expected at least 4 hooks'],
|
||||
['rooms', 5, 'Expected at least 5 rooms'],
|
||||
['encounters', 6, 'Expected at least 6 encounters'],
|
||||
['npcs', 4, 'Expected at least 4 NPCs'],
|
||||
['treasure', 4, 'Expected at least 4 treasures'],
|
||||
['randomEvents', 6, 'Expected 6 random events'],
|
||||
['plotResolutions', 4, 'Expected at least 4 plot resolutions']
|
||||
];
|
||||
checks.forEach(([key, min, msg]) => {
|
||||
const val = dungeonData[key];
|
||||
if (!val || (Array.isArray(val) ? val.length < min : val.trim().length < min)) {
|
||||
issues.push(`${msg}${Array.isArray(val) ? `, got ${val?.length || 0}` : ''}`);
|
||||
}
|
||||
});
|
||||
dungeonData.rooms?.forEach((r, i) => {
|
||||
if (!r.description || r.description.trim().length < 20) {
|
||||
issues.push(`Room ${i + 1} (${r.name}) description too short`);
|
||||
}
|
||||
});
|
||||
dungeonData.encounters?.forEach((e, i) => {
|
||||
if (!e.details || e.details.trim().length < 30) {
|
||||
issues.push(`Encounter ${i + 1} (${e.name}) details too short`);
|
||||
}
|
||||
});
|
||||
dungeonData.npcs?.forEach((n, i) => {
|
||||
if (!n.trait || n.trait.trim().length < 30) {
|
||||
issues.push(`NPC ${i + 1} (${n.name}) description too short`);
|
||||
}
|
||||
});
|
||||
return issues;
|
||||
}
|
||||
|
||||
export function validateContentQuality(dungeonData) {
|
||||
const issues = [];
|
||||
const vagueWords = /\b(some|various|several|many|few|things|stuff|items|objects)\b/gi;
|
||||
const checkVague = (text, ctx) => {
|
||||
if (!text) return;
|
||||
const matches = text.match(vagueWords);
|
||||
if (matches?.length > 2) {
|
||||
issues.push(`${ctx} contains vague language: "${matches.slice(0, 3).join('", "')}"`);
|
||||
}
|
||||
};
|
||||
checkVague(dungeonData.flavor, 'Flavor text');
|
||||
dungeonData.rooms?.forEach(r => checkVague(r.description, `Room "${r.name}"`));
|
||||
dungeonData.encounters?.forEach(e => checkVague(e.details, `Encounter "${e.name}"`));
|
||||
dungeonData.npcs?.forEach(n => checkVague(n.trait, `NPC "${n.name}"`));
|
||||
dungeonData.rooms?.forEach(r => {
|
||||
if (r.description?.length < 50) {
|
||||
issues.push(`Room "${r.name}" description too short`);
|
||||
}
|
||||
});
|
||||
return issues;
|
||||
}
|
||||
|
||||
export function validateContentStructure(dungeonData) {
|
||||
const issues = [];
|
||||
dungeonData.rooms?.forEach((r, i) => {
|
||||
if (!r.name?.trim()) issues.push(`Room ${i + 1} missing name`);
|
||||
if (r.name?.split(/\s+/).length > 6) issues.push(`Room "${r.name}" name too long`);
|
||||
});
|
||||
dungeonData.encounters?.forEach((e, i) => {
|
||||
if (!e.name?.trim()) issues.push(`Encounter ${i + 1} missing name`);
|
||||
if (e.name?.split(/\s+/).length > 6) issues.push(`Encounter "${e.name}" name too long`);
|
||||
if (e.details && !e.details.match(/^[^:]+:\s/)) {
|
||||
issues.push(`Encounter "${e.name}" details missing location prefix`);
|
||||
}
|
||||
});
|
||||
dungeonData.npcs?.forEach((n, i) => {
|
||||
if (!n.name?.trim()) issues.push(`NPC ${i + 1} missing name`);
|
||||
if (n.name?.split(/\s+/).length > 4) issues.push(`NPC "${n.name}" name too long`);
|
||||
});
|
||||
return issues;
|
||||
}
|
||||
|
||||
export function validateNarrativeCoherence(dungeonData) {
|
||||
const issues = [];
|
||||
const factionMatch = dungeonData.coreConcepts?.match(/Primary Faction[:\s]+([^.]+)/i);
|
||||
const factionName = factionMatch?.[1]?.trim();
|
||||
if (dungeonData.encounters && dungeonData.rooms) {
|
||||
const roomNames = dungeonData.rooms.map(r => r.name.trim().toLowerCase());
|
||||
dungeonData.encounters.forEach(e => {
|
||||
const locMatch = e.details?.match(/^([^:]+):/);
|
||||
if (locMatch) {
|
||||
const locName = locMatch[1].trim().toLowerCase();
|
||||
if (!roomNames.some(rn => locName.includes(rn) || rn.includes(locName))) {
|
||||
issues.push(`Encounter "${e.name}" references unknown location "${locMatch[1]}"`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (factionName) {
|
||||
const factionLower = factionName.toLowerCase();
|
||||
const refs = (dungeonData.npcs?.filter(n => n.trait?.toLowerCase().includes(factionLower)).length ?? 0)
|
||||
+ (dungeonData.encounters?.filter(e => e.details?.toLowerCase().includes(factionLower)).length ?? 0);
|
||||
if (refs < 2) {
|
||||
issues.push(`Faction "${factionName}" poorly integrated (${refs} references)`);
|
||||
}
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
Reference in New Issue
Block a user