Initial version

This commit is contained in:
2026-01-30 12:08:22 +02:00
commit db3e46de18
13 changed files with 4248 additions and 0 deletions

38
tests/inline-test.js Normal file
View File

@@ -0,0 +1,38 @@
// Quick inline test - can be run with copy-paste in browser console
// Test cases
const tests = [
{ code: 'DC2.W+++', expect: 'Planets', desc: 'Width +++' },
{ code: 'DC2.W~', expect: 'Variable', desc: 'Width ~' },
{ code: 'DC2.Tc+++[SE]', expect: 'assembly', desc: 'Technology with specialist' },
{ code: 'DC2.T+', expect: 'Over-weight', desc: 'Weight +' },
{ code: 'DC2.Skm', expect: 'metal', desc: 'Skin Type metal' },
{ code: 'DC2.Df-', expect: 'Irritant', desc: 'Dragon Friend -' },
{ code: 'DC2.L', expect: 'Normal', desc: 'Length normal' },
{ code: 'DC2.Ac+++!', expect: 'T1', desc: 'Activity +++!' },
{ code: 'DC2.A+++!', expect: 'Eternal', desc: 'Age +++!' },
{ code: 'DC2.$', expect: 'hoard', desc: 'Money $' },
{ code: 'DC2.O/', expect: 'fault', desc: 'Offspring /' },
];
console.log('=== Running Inline Tests ===\n');
tests.forEach(test => {
try {
const parsed = parseDragonCode(test.code);
const decoded = decodeDragonCode(parsed);
let actual = 'NOT FOUND';
for (const section of Object.values(decoded)) {
if (section && section.length > 0) {
actual = section[0].value;
break;
}
}
const passed = actual.toLowerCase().includes(test.expect.toLowerCase());
console.log(`${passed ? '✓' : '✗'} ${test.desc}: ${actual}`);
} catch (error) {
console.log(`${test.desc}: ERROR - ${error.message}`);
}
});

98
tests/run-tests.html Normal file
View File

@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Dragon Code Tests - Auto Runner</title>
</head>
<body>
<h1>Running Tests...</h1>
<pre id="output"></pre>
<script src="../js/species-data.js"></script>
<script src="../js/tags-data.js"></script>
<script src="../js/parser.js"></script>
<script src="../js/decoder.js"></script>
<script src="test-data.js"></script>
<script src="test-runner.js"></script>
<script>
// Auto-run tests and output to console and page
const output = document.getElementById('output');
const originalLog = console.log;
let logBuffer = [];
console.log = function(...args) {
const message = args.join(' ');
logBuffer.push(message);
output.textContent += message + '\n';
originalLog.apply(console, args);
};
// Run tests
const runner = new TestRunner();
const results = runner.runAll();
const summary = runner.generateSummary();
console.log('\n' + '='.repeat(80));
console.log('DETAILED FAILURES');
console.log('='.repeat(80) + '\n');
// Show only failures
let failureCount = 0;
results.details.forEach(detail => {
if (!detail.passed) {
failureCount++;
console.log(`[${detail.category.toUpperCase()}] ${detail.test}`);
console.log(` Expected: ${JSON.stringify(detail.expected)}`);
console.log(` Got: ${detail.actual || 'null'}`);
if (detail.error) {
console.log(` Error: ${detail.error}`);
}
console.log('');
}
});
if (failureCount === 0) {
console.log('🎉 All tests passed! No failures to report.\n');
}
console.log('='.repeat(80));
console.log('SUMMARY BY CATEGORY');
console.log('='.repeat(80) + '\n');
// Group by category
const categoryStats = {};
results.details.forEach(detail => {
if (!categoryStats[detail.category]) {
categoryStats[detail.category] = { passed: 0, failed: 0, total: 0 };
}
categoryStats[detail.category].total++;
if (detail.passed) {
categoryStats[detail.category].passed++;
} else {
categoryStats[detail.category].failed++;
}
});
for (const [category, stats] of Object.entries(categoryStats)) {
const passRate = ((stats.passed / stats.total) * 100).toFixed(1);
const status = stats.failed === 0 ? '✓' : '✗';
console.log(`${status} ${category.padEnd(20)} ${stats.passed}/${stats.total} (${passRate}%)`);
}
console.log('\n' + '='.repeat(80));
// Create downloadable file
const blob = new Blob([logBuffer.join('\n')], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'test-results.txt';
link.textContent = 'Download Test Results';
link.style.cssText = 'display:block;margin:20px;padding:10px;background:#3498db;color:white;text-decoration:none;border-radius:4px;width:200px;text-align:center';
document.body.insertBefore(link, output);
// Auto-download (commented out - enable if desired)
// link.click();
</script>
</body>
</html>

332
tests/test-data.js Normal file
View File

@@ -0,0 +1,332 @@
// Comprehensive test data extracted from Dragon Code V2.6 specification
// Each test case includes: tag, expected parsing, and expected decoded output
const TEST_CASES = {
species: [
{
code: 'DC2.Dw',
tag: 'Dw',
expected: {
parse: { type: 'simple', value: 'Dw' },
decode: 'Western Dragon'
}
},
{
code: 'DC2.De',
tag: 'De',
expected: {
parse: { type: 'simple', value: 'De' },
decode: 'Eastern Dragon'
}
},
{
code: 'DC2.H',
tag: 'H',
expected: {
parse: { type: 'simple', value: 'H' },
decode: 'Human'
}
},
{
code: 'DC2.~Dw/H',
tag: '~Dw/H',
expected: {
parse: { type: 'shapechanger' },
decode: 'I change between Western Dragon and Human form'
}
},
{
code: 'DC2.Dw+H',
tag: 'Dw+H',
expected: {
parse: { type: 'cross' },
decode: 'A cross between Western Dragon and Human'
}
},
{
code: 'DC2.D[H]',
tag: 'D[H]',
expected: {
parse: { type: 'trapped' },
decode: 'trapped in Human form'
}
}
],
gender: [
{ code: 'Gm', expected: 'Male' },
{ code: 'Gf', expected: 'Female' },
{ code: 'Gh', expected: 'Hermaphrodite' },
{ code: 'Gn', expected: 'Neuter' },
{ code: 'G?', expected: 'not telling' },
{ code: 'G~', expected: 'variable' }
],
length: [
{ code: 'L+++!', expected: 'Celestial' },
{ code: 'L+++', expected: 'Mistaken for mountain ranges' },
{ code: 'L++', expected: 'Lair fits a regiment' },
{ code: 'L+', expected: 'Bigger than most dragons' },
{ code: 'L', expected: 'Normal (Draco-sized)' },
{ code: 'L-', expected: 'Smaller than most dragons' },
{ code: 'L--', expected: 'Fits on your shoulder' },
{ code: 'L---', expected: 'Fits in your pocket' },
{ code: 'L---!', expected: 'Microscopic' },
{ code: 'L10m', expected: '10 meters' },
{ code: 'L10f', expected: '10 feet' },
{ code: 'L10m4t', expected: '10 meters (with 4meters tail length)' }
],
width: [
{ code: 'W+++!', expected: "I am Athelind! My belly is now several galaxies wide" },
{ code: 'W+++', expected: "Planets have been known to crack in half with my arrival!" },
{ code: 'W++', expected: "My digestion of food has been known to cause earthquakes" },
{ code: 'W+', expected: "I move by rolling" },
{ code: 'W', expected: "I'm normal" },
{ code: 'W~', expected: "Variable" },
{ code: 'W-', expected: "slightly on the slim side" },
{ code: 'W--', expected: "serpentine" },
{ code: 'W---', expected: "long string with wings" },
{ code: 'W---!', expected: "one-dimensional" }
],
weight: [
{ code: 'T+++', expected: 'Obese' },
{ code: 'T++', expected: 'Fat' },
{ code: 'T+', expected: 'Over-weight' },
{ code: 'T', expected: 'Normal weight' },
{ code: 'T-', expected: 'Under-weight' },
{ code: 'T--', expected: 'Skeleton with scales' },
{ code: 'T---', expected: 'Anorexic' }
],
skinType: [
{ code: 'Sks', expected: 'scales' },
{ code: 'Skh', expected: 'hide' },
{ code: 'Sku', expected: 'fur' },
{ code: 'Skk', expected: 'skin' },
{ code: 'Skl', expected: 'leather' },
{ code: 'Skm', expected: 'metal' },
{ code: 'Skr', expected: 'rock' },
{ code: 'Skf', expected: 'feathers' },
{ code: 'Ske', expected: 'exoskeleton' },
{ code: 'Skb', expected: 'bark' },
{ code: 'Skc', expected: 'cellulose' },
{ code: 'Skx', expected: 'crystals' },
{ code: 'Sk?', expected: 'Unknown' },
{ code: 'Sks,ak', expected: 'scales, with skin on arms' },
{ code: 'Sks,bm', expected: 'scales, with metal on belly' }
],
age: [
{ code: 'A+++!', expected: 'Eternal' },
{ code: 'A+++', expected: 'Ancient beyond measure' },
{ code: 'A++', expected: 'Ancient' },
{ code: 'A+', expected: "You've been around" },
{ code: 'A', expected: 'Adult' },
{ code: 'A-', expected: 'Young adult' },
{ code: 'A--', expected: 'Adolescent' },
{ code: 'A---', expected: 'Hatchling' },
{ code: 'A?', expected: 'Unknown age' }
],
breath: [
{ code: 'B+++', expected: 'Legendary breath weapon' },
{ code: 'B++', expected: 'Very powerful breath' },
{ code: 'B+', expected: 'Above average breath' },
{ code: 'B', expected: 'Normal breath weapon' },
{ code: 'B-', expected: 'Weak breath' },
{ code: 'B--', expected: 'Very weak breath' },
{ code: 'B---', expected: 'No breath weapon' },
{ code: 'Bfl', expected: 'flame' },
{ code: 'Bfi', expected: 'fire' },
{ code: 'Bac', expected: 'acid' },
{ code: 'Bic', expected: 'ice' },
{ code: 'Bli', expected: 'lightning' }
],
reality: [
{ code: 'R+++', expected: 'I AM a dragon' },
{ code: 'R++', expected: 'Strongly identify with on-line form' },
{ code: 'R+', expected: 'Identify with character' },
{ code: 'R', expected: 'Just playing' },
{ code: 'R-', expected: "It's only a game" },
{ code: 'R--', expected: "Don't confuse RL with online" },
{ code: 'R---', expected: 'Completely separate' }
],
activity: [
{ code: 'Ac+++!', expected: 'T1 connection, never off the net' },
{ code: 'Ac+++', expected: 'Online most of the time' },
{ code: 'Ac++', expected: 'Online frequently' },
{ code: 'Ac+', expected: 'Online regularly' },
{ code: 'Ac', expected: 'Moderate activity' },
{ code: 'Ac-', expected: 'Occasional visits' },
{ code: 'Ac--', expected: 'Rare appearances' },
{ code: 'Ac---', expected: 'Almost never online' }
],
technology: [
{ code: 'Tc+++', expected: 'Program in assembly' },
{ code: 'Tc++', expected: 'Expert programmer' },
{ code: 'Tc+', expected: 'Competent with tech' },
{ code: 'Tc', expected: 'Normal tech skills' },
{ code: 'Tc-', expected: 'Basic computer use' },
{ code: 'Tc--', expected: 'Technology challenged' },
{ code: 'Tc---', expected: "What's a computer?" },
{ code: 'Tc+++[SE]', expected: 'Program in assembly, specialist in SE' }
],
mating: [
{ code: 'M+++!1', expected: 'Mated in RL to online mate\'s RL self, 1 mate' },
{ code: 'M+++', expected: 'Mated to RL person I met online' },
{ code: 'M++', expected: 'Mated online, not met in RL' },
{ code: 'M+', expected: 'Mated/married' },
{ code: 'M', expected: 'Dating' },
{ code: 'M-', expected: 'Looking' },
{ code: 'M--', expected: 'Not interested' },
{ code: 'M---', expected: 'Sworn celibate' }
],
offspring: [
{ code: 'O+++', expected: 'Large family' },
{ code: 'O++', expected: 'Several offspring' },
{ code: 'O+', expected: 'Have offspring' },
{ code: 'O', expected: 'Interested in having' },
{ code: 'O-', expected: 'Not interested' },
{ code: 'O--', expected: "Don't even think about it" },
{ code: 'O---', expected: 'Absolutely not' },
{ code: 'O/', expected: "If one was my fault, I'd faint" }
],
hoard: [
{ code: 'DC2.Dw H+++', expected: "Really can't tell when someone steals" },
{ code: 'DC2.Dw H++', expected: 'Large hoard' },
{ code: 'DC2.Dw H+', expected: 'Growing collection' },
{ code: 'DC2.Dw H', expected: 'Small hoard' },
{ code: 'DC2.Dw H-', expected: 'Starting out' },
{ code: 'DC2.Dw H--', expected: 'No hoard yet' },
{ code: 'DC2.Dw H---', expected: 'No interest in hoarding' }
],
money: [
{ code: '$$$', expected: 'Richest dragon alive' },
{ code: '$$', expected: 'Wealthy' },
{ code: '$', expected: "Don't touch my hoard" },
{ code: '$-', expected: 'Getting by' },
{ code: '$--', expected: 'Poor' },
{ code: '$---', expected: 'Broke' }
],
diet: [
{ code: 'F+++o', expected: 'Glutton omnivore' },
{ code: 'F++o', expected: 'Overindulgent omnivore' },
{ code: 'F+o', expected: 'Healthy appetite omnivore' },
{ code: 'Fc', expected: 'carnivore' },
{ code: 'Fh', expected: 'herbivore' },
{ code: 'Fo', expected: 'omnivore' },
{ code: 'Fv', expected: 'vegetarian' }
],
humor: [
{ code: 'J+++', expected: 'Constantly joking' },
{ code: 'J++', expected: 'Laughing is good for you' },
{ code: 'J+', expected: 'Good sense of humor' },
{ code: 'J', expected: 'Normal humor' },
{ code: 'J-', expected: 'Serious' },
{ code: 'J--', expected: 'Very serious' },
{ code: 'J---', expected: 'No sense of humor' }
],
social: [
{ code: 'S+++', expected: 'Everyone knows' },
{ code: 'S++', expected: 'Most people know' },
{ code: 'S+', expected: 'Several people know' },
{ code: 'S', expected: 'A few friends know' },
{ code: 'S-', expected: 'Very select few know' },
{ code: 'S--', expected: 'One or two people know' },
{ code: 'S---', expected: 'Complete secret' },
{ code: 'S!', expected: 'Out and proud' }
],
ubiquity: [
{ code: 'U+++', expected: 'Legendary status' },
{ code: 'U++', expected: 'Well known' },
{ code: 'U+', expected: 'Known in community' },
{ code: 'U', expected: 'Regular member' },
{ code: 'U-', expected: 'Pretty sure did something important' },
{ code: 'U--', expected: 'Mostly unnoticed' },
{ code: 'U---', expected: 'Unknown' }
],
irritability: [
{ code: 'I+++', expected: 'Constantly angry' },
{ code: 'I++', expected: 'Short temper' },
{ code: 'I+', expected: 'Get annoyed easily' },
{ code: 'I', expected: 'Normal temperament' },
{ code: 'I-', expected: 'Patient' },
{ code: 'I--', expected: 'Take everything in stride' },
{ code: 'I---', expected: 'Nothing bothers me' }
],
magic: [
{ code: 'V+++', expected: 'Archmage level' },
{ code: 'V++', expected: 'Powerful magic' },
{ code: 'V+', expected: 'Competent magic user' },
{ code: 'V', expected: 'Some magical ability' },
{ code: 'V-', expected: 'Minor magic' },
{ code: 'V--', expected: 'Magicians worry when near' },
{ code: 'V---', expected: 'Magic has no effect' }
],
psyPower: [
{ code: 'Q+++', expected: 'Master psychic' },
{ code: 'Q++', expected: 'Strong psychic' },
{ code: 'Q+', expected: 'Psychic abilities' },
{ code: 'Q', expected: 'Minor psychic talent' },
{ code: 'Q-', expected: 'Resistant to psionics' },
{ code: 'Q--', expected: 'Immune to psionics' },
{ code: 'Q---', expected: 'Psionics have no effect' }
],
emotion: [
{ code: 'E+++', expected: 'Extremely affectionate' },
{ code: 'E++', expected: 'Fairly free with hugs' },
{ code: 'E+', expected: 'Affectionate' },
{ code: 'E', expected: 'Normal emotional expression' },
{ code: 'E-', expected: 'Reserved' },
{ code: 'E--', expected: 'Emotionally distant' },
{ code: 'E---', expected: 'Cold/unemotional' }
],
dragonFriend: [
{ code: 'Df+++!', expected: "Have you noticed? You've started growing scales!" },
{ code: 'Df+++', expected: 'Popular with dragons - you exchange overnight visits' },
{ code: 'Df++', expected: 'Reasonably popular - you exchange social visits' },
{ code: 'Df+', expected: 'Polite acquaintance - you exchange social pleasantries' },
{ code: 'Df', expected: "Tolerance - they don't eat you" },
{ code: 'Df-', expected: 'Irritant - they think about having you over for lunch!' },
{ code: 'Df--', expected: 'Maddening - they think about a quick snack ... now!' },
{ code: 'Df---', expected: "Infuriating - you're not good enough for a snack" },
{ code: 'Df---!', expected: "Cold fury - they're going to hunt you, and find you, and..." }
]
};
// Full integration test cases
const INTEGRATION_TESTS = [
{
name: 'Full Example from Spec',
code: 'DC2.Dw Gm L W T+ Phvwalt Sk? Cbk--^\\bl+lum B- A+ N? M+++!1 O/ H+++ $ F+o R++ Ac+++! J++ S U- I-- V- Q--- Tc+++[SE] E++',
expectedTags: {
species: 'Western Dragon',
gender: 'Male',
length: 'Normal (Draco-sized)',
width: "I'm normal",
weight: 'Over-weight',
skinType: 'Unknown',
breath: 'Weak breath',
age: "You've been around",
technology: 'Program in assembly, specialist in SE'
}
}
];

115
tests/test-runner-node.js Normal file
View File

@@ -0,0 +1,115 @@
#!/usr/bin/env node
// Node.js Test Runner for Dragon Code V2.6
// Loads all dependencies and runs tests in Node environment
const fs = require('fs');
const path = require('path');
// Simulate browser environment
global.window = {};
// Load dependencies
function loadScript(filename) {
const filepath = path.join(__dirname, '..', filename);
const content = fs.readFileSync(filepath, 'utf8');
eval(content);
}
// Load all JS files
try {
loadScript('js/species-data.js');
loadScript('js/tags-data.js');
loadScript('js/parser.js');
loadScript('js/decoder.js');
// Load test files
const testDataPath = path.join(__dirname, 'test-data.js');
const testData = fs.readFileSync(testDataPath, 'utf8');
eval(testData);
const testRunnerPath = path.join(__dirname, 'test-runner.js');
const testRunner = fs.readFileSync(testRunnerPath, 'utf8');
eval(testRunner);
} catch (error) {
console.error('Error loading scripts:', error.message);
process.exit(1);
}
// Run tests
console.log('='.repeat(80));
console.log('Dragon Code V2.6 - Automated Test Suite');
console.log('='.repeat(80));
console.log('');
const runner = new TestRunner();
const results = runner.runAll();
const summary = runner.generateSummary();
console.log('');
console.log('='.repeat(80));
console.log('DETAILED FAILURES');
console.log('='.repeat(80));
console.log('');
// Show only failures
let failureCount = 0;
results.details.forEach(detail => {
if (!detail.passed) {
failureCount++;
console.log(`[${detail.category.toUpperCase()}] ${detail.test}`);
console.log(` Expected: ${JSON.stringify(detail.expected)}`);
console.log(` Got: ${detail.actual || 'null'}`);
if (detail.error) {
console.log(` Error: ${detail.error}`);
}
console.log('');
}
});
if (failureCount === 0) {
console.log('🎉 All tests passed! No failures to report.');
}
console.log('');
console.log('='.repeat(80));
console.log('SUMMARY BY CATEGORY');
console.log('='.repeat(80));
console.log('');
// Group by category and show stats
const categoryStats = {};
results.details.forEach(detail => {
if (!categoryStats[detail.category]) {
categoryStats[detail.category] = { passed: 0, failed: 0, total: 0 };
}
categoryStats[detail.category].total++;
if (detail.passed) {
categoryStats[detail.category].passed++;
} else {
categoryStats[detail.category].failed++;
}
});
for (const [category, stats] of Object.entries(categoryStats)) {
const passRate = ((stats.passed / stats.total) * 100).toFixed(1);
const status = stats.failed === 0 ? '✓' : '✗';
console.log(`${status} ${category.padEnd(20)} ${stats.passed}/${stats.total} (${passRate}%)`);
}
console.log('');
console.log('='.repeat(80));
// Write results to file
const outputPath = path.join(__dirname, 'test-results.json');
fs.writeFileSync(outputPath, JSON.stringify({
summary,
results: results.details,
categoryStats
}, null, 2));
console.log(`\nDetailed results written to: ${outputPath}`);
// Exit with error code if tests failed
process.exit(results.failed > 0 ? 1 : 0);

284
tests/test-runner.js Normal file
View File

@@ -0,0 +1,284 @@
// Dragon Code V2.6 Test Runner
// Validates parser and decoder against specification
class TestRunner {
constructor() {
this.results = {
passed: 0,
failed: 0,
total: 0,
details: []
};
}
// Run all tests
runAll() {
console.log('=== Dragon Code V2.6 Test Suite ===\n');
// Test each category
for (const [category, tests] of Object.entries(TEST_CASES)) {
this.runCategory(category, tests);
}
// Run integration tests
this.runIntegrationTests();
return this.results;
}
// Run tests for a specific category
runCategory(category, tests) {
console.log(`\n--- Testing ${category} (${tests.length} tests) ---`);
tests.forEach((test, index) => {
this.runTest(category, test, index);
});
}
// Run a single test
runTest(category, test, index) {
this.results.total++;
try {
// Parse the code
const fullCode = test.code || `DC2.${test.code}`;
const parsed = parseDragonCode(fullCode);
// Decode the parsed result
const decoded = decodeDragonCode(parsed);
// Validate the result
const validation = this.validateResult(category, test, parsed, decoded);
if (validation.passed) {
this.results.passed++;
console.log(`✓ Test ${index + 1}: ${test.code}`);
} else {
this.results.failed++;
console.log(`✗ Test ${index + 1}: ${test.code}`);
console.log(` Expected: ${test.expected}`);
console.log(` Got: ${validation.actual}`);
}
this.results.details.push({
category,
test: test.code,
passed: validation.passed,
expected: test.expected,
actual: validation.actual,
error: validation.error
});
} catch (error) {
this.results.failed++;
console.log(`✗ Test ${index + 1}: ${test.code} - ERROR: ${error.message}`);
this.results.details.push({
category,
test: test.code,
passed: false,
expected: test.expected,
actual: null,
error: error.message
});
}
}
// Validate parsed and decoded results
validateResult(category, test, parsed, decoded) {
const result = { passed: false, actual: null, error: null };
try {
// Extract the relevant decoded value based on category
let actual = this.extractDecodedValue(category, decoded);
if (!actual) {
result.error = 'No decoded value found';
result.actual = 'null';
return result;
}
result.actual = actual;
// Check if the decoded value contains the expected text
const expected = test.expected;
if (typeof expected === 'string') {
// For simple string matching, check if the actual contains expected keywords
const actualLower = actual.toLowerCase();
const expectedLower = expected.toLowerCase();
// If expected is very short, do exact match
if (expectedLower.length <= 5) {
result.passed = actualLower === expectedLower || actualLower.includes(expectedLower);
} else {
// Match if actual contains most of the expected words
const expectedWords = expectedLower.split(' ').filter(w => w.length > 3);
const matchedWords = expectedWords.filter(word => actualLower.includes(word));
result.passed = matchedWords.length >= Math.max(1, expectedWords.length * 0.6);
}
} else if (typeof expected === 'object') {
// For complex validation (species with parse structure)
result.passed = this.validateComplex(expected, parsed, actual);
}
} catch (error) {
result.error = error.message;
}
return result;
}
// Extract decoded value for a specific category
extractDecodedValue(category, decoded) {
const categoryMap = {
species: { section: 'species', label: 'Species' },
gender: { section: 'physical', label: 'Gender' },
length: { section: 'physical', label: 'Length' },
width: { section: 'physical', label: 'Width' },
weight: { section: 'physical', label: 'Weight' },
age: { section: 'physical', label: 'Age' },
skinType: { section: 'appearance', label: 'Skin Type' },
breath: { section: 'abilities', label: 'Breath Weapon' },
reality: { section: 'personality', label: 'Reality' },
activity: { section: 'personality', label: 'Activity' },
humor: { section: 'personality', label: 'Humor' },
social: { section: 'personality', label: 'Social' },
irritability: { section: 'personality', label: 'Irritability' },
emotion: { section: 'personality', label: 'Emotion' },
technology: { section: 'other', label: 'Technology' },
ubiquity: { section: 'other', label: 'Ubiquity' },
dragonFriend: { section: 'other', label: 'Dragon-Friend' },
mating: { section: 'life', label: 'Mating' },
offspring: { section: 'life', label: 'Offspring' },
hoard: { section: 'life', label: 'Hoard' },
money: { section: 'life', label: 'Money' },
diet: { section: 'life', label: 'Diet' },
magic: { section: 'abilities', label: 'Magic' },
psyPower: { section: 'abilities', label: 'Psy-Power' }
};
const mapping = categoryMap[category];
if (!mapping || !decoded[mapping.section]) {
return null;
}
// Find the item with matching label in the section
const items = decoded[mapping.section];
if (!items || items.length === 0) {
return null;
}
// Search for item with matching label
const matchingItem = items.find(item => item.label === mapping.label);
if (matchingItem) {
return matchingItem.value;
}
// Fallback: if no exact match, return first item (for backward compatibility)
return items[0].value;
}
// Validate complex expectations
validateComplex(expected, parsed, actual) {
if (expected.parse) {
// Validate parse structure
if (parsed.species && parsed.species.type !== expected.parse.type) {
return false;
}
}
if (expected.decode) {
// Validate decoded output contains expected text
const actualLower = actual.toLowerCase();
const expectedLower = expected.decode.toLowerCase();
return actualLower.includes(expectedLower);
}
return false;
}
// Run integration tests
runIntegrationTests() {
console.log('\n--- Integration Tests ---');
INTEGRATION_TESTS.forEach((test, index) => {
this.results.total++;
try {
const parsed = parseDragonCode(test.code);
const decoded = decodeDragonCode(parsed);
let allPassed = true;
const failures = [];
// Check each expected tag
for (const [tagType, expectedValue] of Object.entries(test.expectedTags)) {
const actual = this.extractDecodedValue(tagType, decoded);
if (!actual || !actual.toLowerCase().includes(expectedValue.toLowerCase().substring(0, 20))) {
allPassed = false;
failures.push(`${tagType}: expected "${expectedValue}", got "${actual}"`);
}
}
if (allPassed) {
this.results.passed++;
console.log(`✓ Integration Test ${index + 1}: ${test.name}`);
} else {
this.results.failed++;
console.log(`✗ Integration Test ${index + 1}: ${test.name}`);
failures.forEach(f => console.log(` ${f}`));
}
this.results.details.push({
category: 'integration',
test: test.name,
passed: allPassed,
expected: test.expectedTags,
actual: allPassed ? 'All tags matched' : failures.join('; ')
});
} catch (error) {
this.results.failed++;
console.log(`✗ Integration Test ${index + 1}: ${test.name} - ERROR: ${error.message}`);
this.results.details.push({
category: 'integration',
test: test.name,
passed: false,
error: error.message
});
}
});
}
// Generate summary report
generateSummary() {
const passRate = ((this.results.passed / this.results.total) * 100).toFixed(1);
console.log('\n=== Test Summary ===');
console.log(`Total: ${this.results.total}`);
console.log(`Passed: ${this.results.passed}`);
console.log(`Failed: ${this.results.failed}`);
console.log(`Pass Rate: ${passRate}%`);
return {
total: this.results.total,
passed: this.results.passed,
failed: this.results.failed,
passRate: passRate
};
}
// Get detailed results
getResults() {
return this.results;
}
}
// Auto-run tests if this script is loaded directly
if (typeof window !== 'undefined') {
window.TestRunner = TestRunner;
}