Major Features: • Smart vocabulary dependency analysis - only learn words needed for next content • Discovered vs Mastered word tracking with self-assessment (Again/Hard/Good/Easy) • Vocabulary Knowledge interface connected to DRS PrerequisiteEngine (not flashcard games) • Smart Guide UI adaptation for vocabulary override with clear explanations • Real PrerequisiteEngine with full method support replacing basic fallbacks Technical Implementation: • VocabularyModule: Added discovered words tracking + self-assessment scoring • UnifiedDRS: Vocabulary override detection with Smart Guide signaling • Vocabulary Knowledge: Reads from DRS only, shows discovered vs mastered stats • Smart Guide: Adaptive UI showing "Vocabulary Practice (N words needed)" when overridden • PrerequisiteEngine: Full initialization with analyzeChapter() method Architecture Documentation: • Added comprehensive "Intelligent Content Dependency System" to CLAUDE.md • Content-driven vocabulary acquisition instead of arbitrary percentage-based forcing • Complete implementation plan for smart content analysis and targeted learning 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
715 lines
24 KiB
JavaScript
715 lines
24 KiB
JavaScript
/**
|
|
* 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 = `
|
|
<div class="drs-test-runner">
|
|
<div class="test-header">
|
|
<h2>🧪 DRS Test Suite</h2>
|
|
<p>Tests spécifiques au système DRS (src/DRS/ uniquement)</p>
|
|
<div class="test-controls">
|
|
<button id="run-drs-tests" class="btn-primary">
|
|
🚀 Lancer les Tests DRS
|
|
</button>
|
|
<button id="clear-results" class="btn-secondary">
|
|
🗑️ Effacer les Résultats
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="test-progress" id="test-progress" style="display: none;">
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" id="progress-fill"></div>
|
|
</div>
|
|
<div class="progress-text" id="progress-text">Initialisation...</div>
|
|
</div>
|
|
|
|
<div class="test-results" id="test-results">
|
|
<div class="welcome-message">
|
|
<h3>👋 Bienvenue dans l'interface de tests DRS</h3>
|
|
<p>Cliquez sur "Lancer les Tests DRS" pour commencer la validation du système.</p>
|
|
<div class="test-scope">
|
|
<h4>🎯 Scope des tests :</h4>
|
|
<ul>
|
|
<li>✅ Imports et structure des modules DRS</li>
|
|
<li>✅ Compliance avec ExerciseModuleInterface</li>
|
|
<li>✅ Services DRS (IAEngine, LLMValidator, etc.)</li>
|
|
<li>✅ VocabularyModule (système flashcards intégré)</li>
|
|
<li>✅ Séparation DRS vs Games</li>
|
|
<li>✅ Transitions entre modules</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="test-summary" id="test-summary" style="display: none;">
|
|
<!-- Résumé affiché à la fin -->
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// 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 = '<div class="test-log"></div>';
|
|
|
|
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 += `<div class="test-section">${title}</div>`;
|
|
log.scrollTop = log.scrollHeight;
|
|
}
|
|
|
|
logSuccess(name) {
|
|
const log = document.querySelector('.test-log');
|
|
log.innerHTML += `<div class="test-result success">✅ ${name}</div>`;
|
|
log.scrollTop = log.scrollHeight;
|
|
}
|
|
|
|
logFailure(name, error) {
|
|
const log = document.querySelector('.test-log');
|
|
log.innerHTML += `<div class="test-result failure">❌ ${name}: ${error}</div>`;
|
|
log.scrollTop = log.scrollHeight;
|
|
}
|
|
|
|
logError(message) {
|
|
const log = document.querySelector('.test-log');
|
|
log.innerHTML += `<div class="test-result error">💥 ${message}</div>`;
|
|
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 = `
|
|
<div class="summary-content ${statusClass}">
|
|
<h3>📊 Résultats des Tests DRS</h3>
|
|
<div class="summary-stats">
|
|
<div class="stat">
|
|
<span class="stat-number">${this.tests.total}</span>
|
|
<span class="stat-label">Total</span>
|
|
</div>
|
|
<div class="stat success">
|
|
<span class="stat-number">${this.tests.passed}</span>
|
|
<span class="stat-label">Réussis</span>
|
|
</div>
|
|
<div class="stat failure">
|
|
<span class="stat-number">${this.tests.failed}</span>
|
|
<span class="stat-label">Échecs</span>
|
|
</div>
|
|
<div class="stat rate">
|
|
<span class="stat-number">${successRate}%</span>
|
|
<span class="stat-label">Taux de réussite</span>
|
|
</div>
|
|
</div>
|
|
<div class="summary-status">
|
|
<h4>${status}</h4>
|
|
${this.tests.failed === 0 ?
|
|
'<p>🎯 Tous les tests DRS sont passés ! Le système fonctionne parfaitement.</p>' :
|
|
`<p>⚠️ ${this.tests.failed} test(s) en échec. Vérifiez les détails ci-dessus.</p>`
|
|
}
|
|
</div>
|
|
</div>
|
|
`;
|
|
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 = `
|
|
<div class="welcome-message">
|
|
<h3>👋 Bienvenue dans l'interface de tests DRS</h3>
|
|
<p>Cliquez sur "Lancer les Tests DRS" pour commencer la validation du système.</p>
|
|
<div class="test-scope">
|
|
<h4>🎯 Scope des tests :</h4>
|
|
<ul>
|
|
<li>✅ Imports et structure des modules DRS</li>
|
|
<li>✅ Compliance avec ExerciseModuleInterface</li>
|
|
<li>✅ Services DRS (IAEngine, LLMValidator, etc.)</li>
|
|
<li>✅ VocabularyModule (système flashcards intégré)</li>
|
|
<li>✅ Séparation DRS vs Games</li>
|
|
<li>✅ Transitions entre modules</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
`;
|
|
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; |