Major Changes: - Moved legacy system to Legacy/ folder for archival - Built new modular architecture with strict separation of concerns - Created core system: Module, EventBus, ModuleLoader, Router - Added Application bootstrap with auto-start functionality - Implemented development server with ES6 modules support - Created comprehensive documentation and project context - Converted SBS-7-8 content to JSON format - Copied all legacy games and content to new structure New Architecture Features: - Sealed modules with WeakMap private data - Strict dependency injection system - Event-driven communication only - Inviolable responsibility patterns - Auto-initialization without commands - Component-based UI foundation ready Technical Stack: - Vanilla JS/HTML/CSS only - ES6 modules with proper imports/exports - HTTP development server (no file:// protocol) - Modular CSS with component scoping - Comprehensive error handling and debugging Ready for Phase 2: Converting legacy modules to new architecture 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
289 lines
9.0 KiB
JavaScript
289 lines
9.0 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Script principal pour lancer tous les tests
|
|
* Usage: node tests/run-tests.js [options]
|
|
*/
|
|
|
|
import { spawn } from 'child_process';
|
|
import { readdir } from 'fs/promises';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
// Configuration
|
|
const config = {
|
|
timeout: 30000, // 30 secondes par test
|
|
verbose: process.argv.includes('--verbose'),
|
|
watch: process.argv.includes('--watch'),
|
|
coverage: process.argv.includes('--coverage'),
|
|
pattern: process.argv.find(arg => arg.startsWith('--pattern='))?.split('=')[1] || '*',
|
|
bail: process.argv.includes('--bail'),
|
|
unitOnly: process.argv.includes('--unit-only'),
|
|
integrationOnly: process.argv.includes('--integration-only')
|
|
};
|
|
|
|
// Couleurs pour les logs
|
|
const colors = {
|
|
green: '\x1b[32m',
|
|
red: '\x1b[31m',
|
|
yellow: '\x1b[33m',
|
|
blue: '\x1b[34m',
|
|
reset: '\x1b[0m',
|
|
bold: '\x1b[1m'
|
|
};
|
|
|
|
function log(message, color = 'reset') {
|
|
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
}
|
|
|
|
function logSection(title) {
|
|
log(`\n${'='.repeat(60)}`, 'blue');
|
|
log(`${title}`, 'bold');
|
|
log(`${'='.repeat(60)}`, 'blue');
|
|
}
|
|
|
|
async function findTestFiles(directory, pattern = '*.test.js') {
|
|
try {
|
|
const files = await readdir(directory);
|
|
return files
|
|
.filter(file => file.endsWith('.test.js'))
|
|
.filter(file => pattern === '*' || file.includes(pattern))
|
|
.map(file => path.join(directory, file));
|
|
} catch (error) {
|
|
log(`⚠️ Impossible de lire ${directory}: ${error.message}`, 'yellow');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async function runTests(testFiles, description) {
|
|
if (testFiles.length === 0) {
|
|
log(`📁 Aucun test trouvé pour ${description}`, 'yellow');
|
|
return { success: true, total: 0, passed: 0, failed: 0 };
|
|
}
|
|
|
|
logSection(`🧪 ${description} (${testFiles.length} fichiers)`);
|
|
|
|
const args = ['--test'];
|
|
|
|
if (config.coverage) {
|
|
args.push('--experimental-test-coverage');
|
|
}
|
|
|
|
args.push(...testFiles);
|
|
|
|
return new Promise((resolve) => {
|
|
const nodeProcess = spawn('node', args, {
|
|
stdio: 'pipe',
|
|
cwd: path.resolve(__dirname, '..')
|
|
});
|
|
|
|
let output = '';
|
|
let errorOutput = '';
|
|
|
|
nodeProcess.stdout.on('data', (data) => {
|
|
const text = data.toString();
|
|
output += text;
|
|
if (config.verbose) {
|
|
process.stdout.write(text);
|
|
}
|
|
});
|
|
|
|
nodeProcess.stderr.on('data', (data) => {
|
|
const text = data.toString();
|
|
errorOutput += text;
|
|
if (config.verbose) {
|
|
process.stderr.write(text);
|
|
}
|
|
});
|
|
|
|
nodeProcess.on('close', (code) => {
|
|
const success = code === 0;
|
|
|
|
// Parser les résultats basiques
|
|
const lines = output.split('\n');
|
|
const summary = lines.find(line => line.includes('tests') && line.includes('passed'));
|
|
|
|
let stats = { total: 0, passed: 0, failed: 0 };
|
|
if (summary) {
|
|
const match = summary.match(/(\\d+) passed.*?(\\d+) failed/);
|
|
if (match) {
|
|
stats.passed = parseInt(match[1]);
|
|
stats.failed = parseInt(match[2]);
|
|
stats.total = stats.passed + stats.failed;
|
|
}
|
|
}
|
|
|
|
if (success) {
|
|
log(`✅ ${description} - Tous les tests sont passés!`, 'green');
|
|
} else {
|
|
log(`❌ ${description} - Des tests ont échoué`, 'red');
|
|
if (!config.verbose && errorOutput) {
|
|
log('Erreurs:', 'red');
|
|
console.error(errorOutput);
|
|
}
|
|
}
|
|
|
|
resolve({ success, ...stats, output, errorOutput });
|
|
});
|
|
|
|
// Timeout
|
|
setTimeout(() => {
|
|
nodeProcess.kill();
|
|
log(`⏰ Timeout atteint pour ${description}`, 'yellow');
|
|
resolve({ success: false, total: 0, passed: 0, failed: 0, timeout: true });
|
|
}, config.timeout);
|
|
});
|
|
}
|
|
|
|
async function checkDependencies() {
|
|
log('🔍 Vérification des dépendances...', 'blue');
|
|
|
|
// Vérifier que Node.js supporte --test
|
|
const nodeVersion = process.version;
|
|
const majorVersion = parseInt(nodeVersion.split('.')[0].slice(1));
|
|
|
|
if (majorVersion < 18) {
|
|
log(`❌ Node.js ${nodeVersion} ne supporte pas --test. Version minimale: 18.0.0`, 'red');
|
|
return false;
|
|
}
|
|
|
|
log(`✅ Node.js ${nodeVersion} - Support des tests natif OK`, 'green');
|
|
return true;
|
|
}
|
|
|
|
async function setupEnvironment() {
|
|
log('🔧 Configuration de l\'environnement de test...', 'blue');
|
|
|
|
// Variables d'environnement pour les tests
|
|
process.env.NODE_ENV = 'test';
|
|
process.env.TEST_VERBOSE = config.verbose ? '1' : '0';
|
|
|
|
// Désactiver les logs en mode non-verbose
|
|
if (!config.verbose) {
|
|
process.env.SILENT_TESTS = '1';
|
|
}
|
|
|
|
log('✅ Environnement configuré', 'green');
|
|
}
|
|
|
|
function printSummary(results) {
|
|
logSection('📊 Résumé des Tests');
|
|
|
|
let totalTests = 0, totalPassed = 0, totalFailed = 0;
|
|
let allSuccess = true;
|
|
|
|
results.forEach(({ description, success, total, passed, failed }) => {
|
|
const status = success ? '✅' : '❌';
|
|
const stats = total > 0 ? ` (${passed}/${total})` : '';
|
|
log(`${status} ${description}${stats}`);
|
|
|
|
totalTests += total;
|
|
totalPassed += passed;
|
|
totalFailed += failed;
|
|
allSuccess = allSuccess && success;
|
|
});
|
|
|
|
log('\\n' + '─'.repeat(40));
|
|
log(`📈 Total: ${totalTests} tests`, 'bold');
|
|
log(`✅ Réussis: ${totalPassed}`, 'green');
|
|
|
|
if (totalFailed > 0) {
|
|
log(`❌ Échoués: ${totalFailed}`, 'red');
|
|
}
|
|
|
|
const successRate = totalTests > 0 ? Math.round((totalPassed / totalTests) * 100) : 100;
|
|
log(`📊 Taux de réussite: ${successRate}%`, successRate === 100 ? 'green' : 'yellow');
|
|
|
|
if (allSuccess && totalTests > 0) {
|
|
log('\\n🎉 Tous les tests sont passés!', 'green');
|
|
} else if (totalFailed > 0) {
|
|
log('\\n💥 Certains tests ont échoué', 'red');
|
|
}
|
|
|
|
return allSuccess;
|
|
}
|
|
|
|
function printUsage() {
|
|
log('📚 Usage: node tests/run-tests.js [options]\\n', 'bold');
|
|
log('Options disponibles:');
|
|
log(' --verbose Affichage détaillé des tests');
|
|
log(' --watch Mode surveillance (redémarre les tests si changement)');
|
|
log(' --coverage Rapport de couverture de code');
|
|
log(' --pattern=PATTERN Ne lancer que les tests contenant PATTERN');
|
|
log(' --bail Arrêter au premier échec');
|
|
log(' --unit-only Lancer seulement les tests unitaires');
|
|
log(' --integration-only Lancer seulement les tests d\'intégration');
|
|
log(' --help Afficher cette aide');
|
|
}
|
|
|
|
async function main() {
|
|
if (process.argv.includes('--help')) {
|
|
printUsage();
|
|
return;
|
|
}
|
|
|
|
logSection('🚀 Class Generator - Suite de Tests');
|
|
|
|
// Vérifications préliminaires
|
|
if (!(await checkDependencies())) {
|
|
process.exit(1);
|
|
}
|
|
|
|
await setupEnvironment();
|
|
|
|
const results = [];
|
|
|
|
try {
|
|
// Tests unitaires
|
|
if (!config.integrationOnly) {
|
|
const unitTestDir = path.join(__dirname, 'unit');
|
|
const unitTests = await findTestFiles(unitTestDir, config.pattern);
|
|
const unitResult = await runTests(unitTests, 'Tests Unitaires');
|
|
results.push({ description: 'Tests Unitaires', ...unitResult });
|
|
|
|
if (config.bail && !unitResult.success) {
|
|
log('🛑 Arrêt après échec des tests unitaires (--bail)', 'red');
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Tests d'intégration
|
|
if (!config.unitOnly) {
|
|
const integrationTestDir = path.join(__dirname, 'integration');
|
|
const integrationTests = await findTestFiles(integrationTestDir, config.pattern);
|
|
const integrationResult = await runTests(integrationTests, 'Tests d\'Intégration');
|
|
results.push({ description: 'Tests d\'Intégration', ...integrationResult });
|
|
|
|
if (config.bail && !integrationResult.success) {
|
|
log('🛑 Arrêt après échec des tests d\'intégration (--bail)', 'red');
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Résumé final
|
|
const allSuccess = printSummary(results);
|
|
|
|
// Code de sortie
|
|
process.exit(allSuccess ? 0 : 1);
|
|
|
|
} catch (error) {
|
|
log(`💥 Erreur lors de l'exécution des tests: ${error.message}`, 'red');
|
|
console.error(error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Mode watch
|
|
if (config.watch) {
|
|
log('👀 Mode surveillance activé - Ctrl+C pour arrêter', 'yellow');
|
|
// TODO: Implémenter la surveillance des fichiers
|
|
log('⚠️ Mode watch non encore implémenté', 'yellow');
|
|
}
|
|
|
|
// Lancement
|
|
main().catch(error => {
|
|
console.error('Erreur fatale:', error);
|
|
process.exit(1);
|
|
}); |