Initial version
This commit is contained in:
284
tests/test-runner.js
Normal file
284
tests/test-runner.js
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user