Class_generator/test-factory.html
StillHammer 44f43324cf Implement ExerciseFactory with strict DRS interface enforcement
🏭 **MAJOR ARCHITECTURE - Factory Pattern Implementation**

**ExerciseFactory Created** (src/DRS/factories/ExerciseFactory.js):
-  Unified creation system for all 11 DRS exercise modules
-  Strict DRSExerciseInterface validation (10 required methods)
-  Smart dependency injection (orchestrator, prerequisiteEngine, llmValidator, contextMemory)
-  Module type mapping: 20 exercise types → 11 module classes
-  Methods: create(), createAndRender(), batchCreate(), destroy()
-  Caching system for loaded module classes
-  Smart warnings (no LLM warning for local-validation modules)

**UnifiedDRS Refactored** (src/DRS/UnifiedDRS.js):
-  Integrated ExerciseFactory for all module loading
-  Created generic helper: _loadExerciseViaFactory()
-  Centralized dependencies: _getDependencies()
-  Simplified loaders: _loadVocabularyModule(), _loadWordDiscoveryModule(), _loadAIAnalysisModule()
-  Removed manual HTML generation - all via factory now
-  165 lines removed, code DRY improved

**Test Suite Created** (test-factory.html):
-  Interactive web-based test interface
-  3 test suites: Validation, Creation (11 modules), Interface compliance
-  Real-time results with success/failure indicators
-  Factory stats display

**Content Enhancement** (test-heavy-stress.json):
-  Added 12 comprehension questions (3 lessons + 1 dialog)
-  lesson1: 3 questions on research methodology
-  lesson2: 3 questions on statistical analysis
-  lesson3: 3 questions on qualitative research
-  conference_presentation: 3 questions on mixed-methods
-  Fixes IntelligentSequencer warnings (now has questions)

**Benefits:**
- 🛡️ Impossible to create modules without interface compliance
- 🔒 Red screen errors force correct implementation
- 📋 Self-documenting via strict contracts
- 🔄 100% DRY - zero code duplication
- 🧪 Fully testable with mock dependencies
- 🎯 Production-ready architecture

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 15:39:53 +08:00

311 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ExerciseFactory Test</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
h1 { color: #333; }
.test-section {
background: white;
padding: 20px;
margin: 20px 0;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.test-item {
padding: 10px;
margin: 5px 0;
border-left: 4px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
}
.test-item.pending { border-color: #ffc107; background: #fff8e1; }
.test-item.success { border-color: #4caf50; background: #e8f5e9; }
.test-item.error { border-color: #f44336; background: #ffebee; }
.status { font-weight: bold; }
.status.pending { color: #f57f17; }
.status.success { color: #2e7d32; }
.status.error { color: #c62828; }
button {
padding: 10px 20px;
background: #2196f3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover { background: #1976d2; }
button:disabled { background: #ccc; cursor: not-allowed; }
.details {
font-size: 12px;
color: #666;
margin-top: 5px;
}
pre {
background: #f5f5f5;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
font-size: 12px;
}
</style>
</head>
<body>
<h1>🏭 ExerciseFactory Test Suite</h1>
<div class="test-section">
<h2>Factory Validation Tests</h2>
<div id="validation-tests"></div>
<button id="run-validation">Run Validation Tests</button>
</div>
<div class="test-section">
<h2>Module Creation Tests (11 modules)</h2>
<div id="creation-tests"></div>
<button id="run-creation">Run Creation Tests</button>
</div>
<div class="test-section">
<h2>Interface Compliance Tests</h2>
<div id="interface-tests"></div>
<button id="run-interface">Run Interface Tests</button>
</div>
<div class="test-section">
<h2>Test Results Summary</h2>
<div id="summary"></div>
</div>
<script type="module">
import exerciseFactory from './src/DRS/factories/ExerciseFactory.js';
const results = {
validation: [],
creation: [],
interface: []
};
// Mock dependencies
const mockDependencies = {
orchestrator: {
name: 'MockOrchestrator',
_eventBus: { emit: () => {} }
},
prerequisiteEngine: {
isDiscovered: () => false,
isWordMastered: () => false,
markDiscovered: () => {},
markMastered: () => {},
init: async () => {}
},
contextMemory: {
recordInteraction: () => {}
},
llmValidator: {
validate: async () => ({ success: true, score: 100 })
}
};
// Validation Tests
document.getElementById('run-validation').addEventListener('click', async () => {
const container = document.getElementById('validation-tests');
container.innerHTML = '<p>Running validation tests...</p>';
const tests = [
{
name: 'Factory exports correctly',
test: () => exerciseFactory !== undefined
},
{
name: 'getSupportedTypes() returns array',
test: () => Array.isArray(exerciseFactory.getSupportedTypes())
},
{
name: 'Has at least 10 supported types',
test: () => exerciseFactory.getSupportedTypes().length >= 10
},
{
name: 'isSupported() works for valid type',
test: () => exerciseFactory.isSupported('vocabulary-flashcards')
},
{
name: 'isSupported() rejects invalid type',
test: () => !exerciseFactory.isSupported('invalid-type')
}
];
container.innerHTML = '';
for (const test of tests) {
try {
const passed = test.test();
results.validation.push({ name: test.name, passed, error: null });
container.innerHTML += `
<div class="test-item ${passed ? 'success' : 'error'}">
<span>${test.name}</span>
<span class="status ${passed ? 'success' : 'error'}">${passed ? '✅ PASS' : '❌ FAIL'}</span>
</div>
`;
} catch (error) {
results.validation.push({ name: test.name, passed: false, error: error.message });
container.innerHTML += `
<div class="test-item error">
<span>${test.name}</span>
<span class="status error">❌ ERROR: ${error.message}</span>
</div>
`;
}
}
updateSummary();
});
// Creation Tests
document.getElementById('run-creation').addEventListener('click', async () => {
const container = document.getElementById('creation-tests');
container.innerHTML = '<p>Running creation tests (this may take a moment)...</p>';
const exerciseTypes = exerciseFactory.getSupportedTypes().slice(0, 11); // Test first 11
container.innerHTML = '';
for (const type of exerciseTypes) {
const testDiv = document.createElement('div');
testDiv.className = 'test-item pending';
testDiv.innerHTML = `
<span>Creating ${type}...</span>
<span class="status pending">⏳ PENDING</span>
`;
container.appendChild(testDiv);
try {
const mockContent = {
vocabulary: [{ word: 'test', translation: 'test' }],
dialogues: [{ content: 'test dialog' }],
chapterContent: { vocabulary: {} }
};
const module = await exerciseFactory.create(
type,
mockDependencies,
{ difficulty: 'medium' },
mockContent
);
// Check if module has required methods
const hasRequiredMethods =
typeof module.render === 'function' &&
typeof module.destroy === 'function' &&
typeof module.validate === 'function' &&
typeof module.getExerciseType === 'function';
if (hasRequiredMethods) {
testDiv.className = 'test-item success';
testDiv.innerHTML = `
<span>✅ ${type}</span>
<span class="status success">SUCCESS</span>
`;
results.creation.push({ name: type, passed: true, error: null });
// Clean up
await module.destroy();
} else {
throw new Error('Missing required methods');
}
} catch (error) {
testDiv.className = 'test-item error';
testDiv.innerHTML = `
<div>
<span>❌ ${type}</span>
<div class="details">${error.message}</div>
</div>
<span class="status error">FAILED</span>
`;
results.creation.push({ name: type, passed: false, error: error.message });
}
}
updateSummary();
});
// Interface Tests
document.getElementById('run-interface').addEventListener('click', async () => {
const container = document.getElementById('interface-tests');
container.innerHTML = '<p>Running interface compliance tests...</p>';
const requiredMethods = [
'init', 'render', 'destroy', 'validate',
'getResults', 'handleUserInput', 'markCompleted',
'getProgress', 'getExerciseType', 'getExerciseConfig'
];
try {
const mockContent = {
vocabulary: [{ word: 'test', translation: 'test' }]
};
const module = await exerciseFactory.create(
'vocabulary-flashcards',
mockDependencies,
{},
mockContent
);
container.innerHTML = '';
for (const method of requiredMethods) {
const exists = typeof module[method] === 'function';
results.interface.push({ name: `Method: ${method}`, passed: exists, error: null });
container.innerHTML += `
<div class="test-item ${exists ? 'success' : 'error'}">
<span>${method}()</span>
<span class="status ${exists ? 'success' : 'error'}">${exists ? '✅ EXISTS' : '❌ MISSING'}</span>
</div>
`;
}
await module.destroy();
} catch (error) {
container.innerHTML = `
<div class="test-item error">
<span>Interface test failed</span>
<span class="status error">❌ ERROR: ${error.message}</span>
</div>
`;
}
updateSummary();
});
function updateSummary() {
const summary = document.getElementById('summary');
const allTests = [...results.validation, ...results.creation, ...results.interface];
const passed = allTests.filter(t => t.passed).length;
const failed = allTests.filter(t => !t.passed).length;
const total = allTests.length;
const percentage = total > 0 ? Math.round((passed / total) * 100) : 0;
summary.innerHTML = `
<h3>📊 Overall Results</h3>
<div style="font-size: 24px; margin: 20px 0;">
<strong>${passed}/${total}</strong> tests passed (${percentage}%)
</div>
<div class="test-item ${percentage === 100 ? 'success' : 'error'}">
<span>✅ Passed: ${passed}</span>
<span>❌ Failed: ${failed}</span>
</div>
<pre>${JSON.stringify(exerciseFactory.getStats(), null, 2)}</pre>
`;
}
// Auto-run validation on load
document.getElementById('run-validation').click();
</script>
</body>
</html>