Files
dragoncode-decoder/tests/test-runner.js

292 lines
10 KiB
JavaScript

// 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' },
appendages: { section: 'appearance', label: 'Appendages' },
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' },
nativeLand: { section: 'life', label: 'Native-Land' },
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;
}
// Export for Node.js (CommonJS)
if (typeof module !== 'undefined' && module.exports) {
module.exports = { TestRunner };
}