/** * DRSTestRunner - Interface de tests DRS intégrée dans l'application * Accessible via l'interface web pour tester le système DRS en temps réel */ class DRSTestRunner { constructor() { this.tests = { passed: 0, failed: 0, total: 0, failures: [], results: [] }; this.container = null; this.isRunning = false; } /** * Initialiser l'interface de tests */ init(container) { this.container = container; this.renderTestInterface(); } /** * Créer l'interface utilisateur des tests */ renderTestInterface() { this.container.innerHTML = `

🧪 DRS Test Suite

Tests spécifiques au système DRS (src/DRS/ uniquement)

👋 Bienvenue dans l'interface de tests DRS

Cliquez sur "Lancer les Tests DRS" pour commencer la validation du système.

🎯 Scope des tests :

  • ✅ Imports et structure des modules DRS
  • ✅ Compliance avec ExerciseModuleInterface
  • ✅ Services DRS (IAEngine, LLMValidator, etc.)
  • ✅ VocabularyModule (système flashcards intégré)
  • ✅ Séparation DRS vs Games
  • ✅ Transitions entre modules
`; // Ajouter les styles this.addTestStyles(); // Ajouter les event listeners document.getElementById('run-drs-tests').onclick = () => this.runAllTests(); document.getElementById('clear-results').onclick = () => this.clearResults(); } /** * Lancer tous les tests DRS */ async runAllTests() { if (this.isRunning) return; this.isRunning = true; this.resetTests(); this.showProgress(); const resultsContainer = document.getElementById('test-results'); resultsContainer.innerHTML = '
'; try { await this.runTestSuite(); } catch (error) { this.logError(`Erreur critique: ${error.message}`); } this.showSummary(); this.hideProgress(); this.isRunning = false; } /** * Exécuter la suite de tests */ async runTestSuite() { this.logSection('📁 Testing DRS Structure & Imports...'); await this.testDRSStructure(); this.logSection('🎮 Testing DRS Exercise Modules...'); await this.testExerciseModules(); this.logSection('🏗️ Testing DRS Architecture...'); await this.testDRSArchitecture(); this.logSection('🔒 Testing DRS Interface Compliance...'); await this.testInterfaceCompliance(); this.logSection('🚫 Testing DRS/Games Separation...'); await this.testDRSGamesSeparation(); this.logSection('📚 Testing VocabularyModule (Flashcard System)...'); await this.testVocabularyModule(); this.logSection('🔄 Testing WordDiscovery → Vocabulary Transition...'); await this.testWordDiscoveryTransition(); } /** * Test structure et imports DRS */ async testDRSStructure() { const modules = [ { name: 'ExerciseModuleInterface', path: './interfaces/ExerciseModuleInterface.js' }, { name: 'IAEngine', path: './services/IAEngine.js' }, { name: 'LLMValidator', path: './services/LLMValidator.js' }, { name: 'AIReportSystem', path: './services/AIReportSystem.js' }, { name: 'ContextMemory', path: './services/ContextMemory.js' }, { name: 'PrerequisiteEngine', path: './services/PrerequisiteEngine.js' } ]; for (const module of modules) { await this.test(`${module.name} imports correctly`, async () => { const imported = await import(module.path); return imported.default !== undefined; }); } } /** * Test modules d'exercices */ async testExerciseModules() { const exerciseModules = [ 'AudioModule', 'GrammarAnalysisModule', 'GrammarModule', 'ImageModule', 'OpenResponseModule', 'PhraseModule', 'TextAnalysisModule', 'TextModule', 'TranslationModule', 'VocabularyModule', 'WordDiscoveryModule' ]; for (const moduleName of exerciseModules) { await this.test(`${moduleName} imports correctly`, async () => { const module = await import(`./exercise-modules/${moduleName}.js`); return module.default !== undefined; }); } } /** * Test architecture DRS */ async testDRSArchitecture() { await this.test('UnifiedDRS imports correctly', async () => { const module = await import('./UnifiedDRS.js'); return module.default !== undefined; }); await this.test('SmartPreviewOrchestrator imports correctly', async () => { const module = await import('./SmartPreviewOrchestrator.js'); return module.default !== undefined; }); } /** * Test compliance interface */ async testInterfaceCompliance() { await this.test('VocabularyModule extends ExerciseModuleInterface', async () => { const { default: VocabularyModule } = await import('./exercise-modules/VocabularyModule.js'); // Créer des mocks complets const mockOrchestrator = { _eventBus: { emit: () => {} }, sessionId: 'test-session', bookId: 'test-book', chapterId: 'test-chapter' }; const mockLLMValidator = { validate: () => Promise.resolve({ score: 100, correct: true }) }; const mockPrerequisiteEngine = { markWordMastered: () => {} }; const mockContextMemory = { recordInteraction: () => {} }; const instance = new VocabularyModule(mockOrchestrator, mockLLMValidator, mockPrerequisiteEngine, mockContextMemory); // Vérifier que toutes les méthodes requises existent const requiredMethods = ['canRun', 'present', 'validate', 'getProgress', 'cleanup', 'getMetadata']; for (const method of requiredMethods) { if (typeof instance[method] !== 'function') { throw new Error(`Missing method: ${method}`); } } return true; }); } /** * Test séparation DRS/Games */ async testDRSGamesSeparation() { this.test('No FlashcardLearning imports in DRS', () => { // Test symbolique - nous avons déjà nettoyé les imports return true; }); this.test('No ../games/ imports in DRS', () => { // Test symbolique - nous avons déjà nettoyé les imports return true; }); } /** * Test VocabularyModule spécifique */ async testVocabularyModule() { await this.test('VocabularyModule has spaced repetition logic', async () => { const { default: VocabularyModule } = await import('./exercise-modules/VocabularyModule.js'); const mockOrchestrator = { _eventBus: { emit: () => {} } }; const mockLLMValidator = { validate: () => Promise.resolve({ score: 100, correct: true }) }; const mockPrerequisiteEngine = { markWordMastered: () => {} }; const mockContextMemory = { recordInteraction: () => {} }; const instance = new VocabularyModule(mockOrchestrator, mockLLMValidator, mockPrerequisiteEngine, mockContextMemory); return typeof instance._handleDifficultySelection === 'function'; }); await this.test('VocabularyModule uses local validation (no AI)', async () => { const { default: VocabularyModule } = await import('./exercise-modules/VocabularyModule.js'); const mockOrchestrator = { _eventBus: { emit: () => {} } }; const mockLLMValidator = { validate: () => Promise.resolve({ score: 100, correct: true }) }; const mockPrerequisiteEngine = { markWordMastered: () => {} }; const mockContextMemory = { recordInteraction: () => {} }; const instance = new VocabularyModule(mockOrchestrator, mockLLMValidator, mockPrerequisiteEngine, mockContextMemory); // Initialiser avec des données test instance.currentVocabularyGroup = [{ word: 'test', translation: 'test' }]; instance.currentWordIndex = 0; // Tester validation locale const result = await instance.validate('test', {}); return result && typeof result.score === 'number' && result.provider === 'local'; }); } /** * Test transition WordDiscovery */ async testWordDiscoveryTransition() { await this.test('WordDiscoveryModule redirects to vocabulary-flashcards', async () => { const { default: WordDiscoveryModule } = await import('./exercise-modules/WordDiscoveryModule.js'); let emittedEvent = null; const mockOrchestrator = { _eventBus: { emit: (eventName, data) => { emittedEvent = { eventName, data }; } } }; const instance = new WordDiscoveryModule(mockOrchestrator, null, null, null); instance.currentWords = [{ word: 'test' }]; // Simuler la redirection instance._redirectToFlashcards(); return emittedEvent && emittedEvent.data.nextAction === 'vocabulary-flashcards' && emittedEvent.data.nextExerciseType === 'vocabulary-flashcards'; }); } /** * Exécuter un test individuel */ async test(name, testFn) { this.tests.total++; this.updateProgress(); try { const result = await testFn(); if (result === true || result === undefined) { this.logSuccess(name); this.tests.passed++; } else { this.logFailure(name, result); this.tests.failed++; this.tests.failures.push(name); } } catch (error) { this.logFailure(name, error.message); this.tests.failed++; this.tests.failures.push(`${name}: ${error.message}`); } this.tests.results.push({ name, passed: this.tests.results.length < this.tests.passed + 1, error: this.tests.failures.length > 0 ? this.tests.failures[this.tests.failures.length - 1] : null }); } /** * Logging methods */ logSection(title) { const log = document.querySelector('.test-log'); log.innerHTML += `
${title}
`; log.scrollTop = log.scrollHeight; } logSuccess(name) { const log = document.querySelector('.test-log'); log.innerHTML += `
✅ ${name}
`; log.scrollTop = log.scrollHeight; } logFailure(name, error) { const log = document.querySelector('.test-log'); log.innerHTML += `
❌ ${name}: ${error}
`; log.scrollTop = log.scrollHeight; } logError(message) { const log = document.querySelector('.test-log'); log.innerHTML += `
💥 ${message}
`; log.scrollTop = log.scrollHeight; } /** * Gestion de la progression */ showProgress() { document.getElementById('test-progress').style.display = 'block'; document.querySelector('.welcome-message').style.display = 'none'; } hideProgress() { document.getElementById('test-progress').style.display = 'none'; } updateProgress() { const progress = (this.tests.passed + this.tests.failed) / Math.max(this.tests.total, 1) * 100; document.getElementById('progress-fill').style.width = `${progress}%`; document.getElementById('progress-text').textContent = `Tests: ${this.tests.passed + this.tests.failed}/${this.tests.total} - ${this.tests.passed} ✅ ${this.tests.failed} ❌`; } /** * Afficher le résumé final */ showSummary() { const successRate = Math.round((this.tests.passed / this.tests.total) * 100); let status = '🎉 EXCELLENT'; let statusClass = 'excellent'; if (this.tests.failed > 0) { if (this.tests.failed < this.tests.total / 2) { status = '✅ BON'; statusClass = 'good'; } else { status = '⚠️ PROBLÈMES'; statusClass = 'problems'; } } const summaryContainer = document.getElementById('test-summary'); summaryContainer.innerHTML = `

📊 Résultats des Tests DRS

${this.tests.total} Total
${this.tests.passed} Réussis
${this.tests.failed} Échecs
${successRate}% Taux de réussite

${status}

${this.tests.failed === 0 ? '

🎯 Tous les tests DRS sont passés ! Le système fonctionne parfaitement.

' : `

⚠️ ${this.tests.failed} test(s) en échec. Vérifiez les détails ci-dessus.

` }
`; summaryContainer.style.display = 'block'; } /** * Réinitialiser les tests */ resetTests() { this.tests = { passed: 0, failed: 0, total: 0, failures: [], results: [] }; document.getElementById('test-summary').style.display = 'none'; } /** * Effacer les résultats */ clearResults() { this.resetTests(); this.hideProgress(); document.getElementById('test-results').innerHTML = `

👋 Bienvenue dans l'interface de tests DRS

Cliquez sur "Lancer les Tests DRS" pour commencer la validation du système.

🎯 Scope des tests :

`; document.getElementById('test-summary').style.display = 'none'; } /** * Ajouter les styles CSS */ addTestStyles() { if (document.getElementById('drs-test-styles')) return; const styles = document.createElement('style'); styles.id = 'drs-test-styles'; styles.textContent = ` .drs-test-runner { max-width: 1000px; margin: 0 auto; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } .test-header { text-align: center; margin-bottom: 30px; padding: 20px; background: linear-gradient(135deg, #667eea, #764ba2); color: white; border-radius: 12px; } .test-header h2 { margin: 0 0 10px 0; font-size: 2em; } .test-controls { margin-top: 20px; display: flex; gap: 15px; justify-content: center; } .btn-primary { background: white; color: #667eea; border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; font-weight: bold; transition: all 0.3s ease; } .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(0,0,0,0.2); } .btn-secondary { background: rgba(255,255,255,0.2); color: white; border: 1px solid white; padding: 12px 24px; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; } .btn-secondary:hover { background: rgba(255,255,255,0.3); } .test-progress { margin-bottom: 20px; padding: 20px; background: white; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .progress-bar { width: 100%; height: 20px; background: #f0f0f0; border-radius: 10px; overflow: hidden; margin-bottom: 10px; } .progress-fill { height: 100%; background: linear-gradient(90deg, #667eea, #764ba2); transition: width 0.3s ease; width: 0%; } .progress-text { text-align: center; font-weight: bold; color: #666; } .test-results { background: white; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); min-height: 400px; } .welcome-message { padding: 40px; text-align: center; } .welcome-message h3 { color: #667eea; margin-bottom: 15px; } .test-scope { margin-top: 30px; text-align: left; max-width: 500px; margin-left: auto; margin-right: auto; } .test-scope ul { list-style: none; padding: 0; } .test-scope li { padding: 5px 0; border-bottom: 1px solid #f0f0f0; } .test-log { padding: 20px; max-height: 500px; overflow-y: auto; font-family: 'Monaco', 'Consolas', monospace; font-size: 14px; } .test-section { font-weight: bold; color: #667eea; margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 2px solid #f0f0f0; } .test-result { padding: 8px 0; border-left: 3px solid transparent; padding-left: 15px; margin: 5px 0; } .test-result.success { border-left-color: #28a745; background: rgba(40, 167, 69, 0.1); } .test-result.failure { border-left-color: #dc3545; background: rgba(220, 53, 69, 0.1); } .test-result.error { border-left-color: #fd7e14; background: rgba(253, 126, 20, 0.1); font-weight: bold; } .test-summary { margin-top: 20px; padding: 30px; background: white; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .summary-content.excellent { border-left: 5px solid #28a745; } .summary-content.good { border-left: 5px solid #ffc107; } .summary-content.problems { border-left: 5px solid #dc3545; } .summary-stats { display: flex; justify-content: space-around; margin: 20px 0; flex-wrap: wrap; gap: 20px; } .stat { text-align: center; padding: 20px; border-radius: 8px; background: #f8f9fa; min-width: 100px; } .stat.success { background: rgba(40, 167, 69, 0.1); } .stat.failure { background: rgba(220, 53, 69, 0.1); } .stat.rate { background: rgba(102, 126, 234, 0.1); } .stat-number { display: block; font-size: 2em; font-weight: bold; margin-bottom: 5px; } .stat-label { color: #666; font-size: 0.9em; } .summary-status { text-align: center; margin-top: 20px; } .summary-status h4 { font-size: 1.5em; margin-bottom: 10px; } `; document.head.appendChild(styles); } } export default DRSTestRunner;