// 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; }