import { readFileSync } from 'fs'; import { fileURLToPath } from 'url'; import path from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Helper pour créer un environnement DOM simulé export function createMockDOM() { // Mock global objects pour les tests global.window = { location: { protocol: 'http:', hostname: 'localhost', port: '8080' }, addEventListener: () => {}, dispatchEvent: () => {}, ContentModules: {}, GameModules: {}, fetch: async (url) => { // Mock fetch pour les tests if (url.includes('sbs-level-7-8-new.json')) { return { ok: true, json: async () => ({ name: "SBS Level 7-8 (New)", description: "Test content", vocabulary: { "test": "test translation" } }) }; } throw new Error(`Mock fetch: URL not handled: ${url}`); } }; global.document = { createElement: (tag) => ({ src: '', onload: null, onerror: null, addEventListener: () => {} }), querySelector: () => null, head: { appendChild: () => {} } }; global.console = { log: () => {}, warn: () => {}, error: () => {}, info: () => {}, debug: () => {} }; // Mock pour logSh global.logSh = (message, level = 'INFO') => { // Silent dans les tests sauf si on veut debug if (process.env.TEST_VERBOSE) { console.log(`[TEST ${level}] ${message}`); } }; } // Helper pour nettoyer l'environnement après tests export function cleanupMockDOM() { delete global.window; delete global.document; delete global.console; delete global.logSh; } // Helper pour charger un fichier JS en tant que module dans les tests export async function loadModuleForTest(relativePath) { const fullPath = path.resolve(__dirname, '../../', relativePath); // Lire le fichier et l'évaluer dans le contexte global const code = readFileSync(fullPath, 'utf8'); // Remplacer les exports/require pour le navigateur const browserCode = code .replace(/export\s+default\s+/g, 'window.TestModule = ') .replace(/export\s+\{([^}]+)\}/g, '') .replace(/import\s+.*?from\s+['"].*?['"];?\s*/g, ''); // Évaluer dans le contexte global eval(browserCode); return global.window.TestModule; } // Helper pour créer des données de test export function createTestContent() { return { name: "Test Content", description: "Content pour les tests", difficulty: "medium", vocabulary: { "hello": "bonjour", "world": "monde", "test": "test" }, sentences: [ { english: "Hello world", chinese: "你好世界", prononciation: "nǐ hǎo shì jiè" } ] }; } // Helper pour simuler des requêtes réseau export function createMockFetch(responses = {}) { return async (url, options = {}) => { if (responses[url]) { const response = responses[url]; return { ok: response.ok !== false, status: response.status || 200, json: async () => response.data, text: async () => JSON.stringify(response.data) }; } // Réponse par défaut return { ok: false, status: 404, json: async () => ({ error: 'Not found in mock' }), text: async () => 'Not found in mock' }; }; } // Helper pour attendre un délai export function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Helper pour capturer les logs export function createLogCapture() { const logs = []; const originalLogSh = global.logSh; global.logSh = (message, level = 'INFO') => { logs.push({ message, level, timestamp: Date.now() }); if (originalLogSh) originalLogSh(message, level); }; return { logs, restore: () => { global.logSh = originalLogSh; }, getLogs: (level) => level ? logs.filter(l => l.level === level) : logs, clear: () => logs.length = 0 }; } // Helper pour les assertions personnalisées export function assertContains(actual, expected, message) { if (!actual.includes(expected)) { throw new Error(message || `Expected "${actual}" to contain "${expected}"`); } } export function assertInstanceOf(actual, expectedClass, message) { if (!(actual instanceof expectedClass)) { throw new Error(message || `Expected instance of ${expectedClass.name}, got ${actual.constructor.name}`); } } // Helper pour mocquer les timers export function createTimerMock() { const timers = new Map(); let timerId = 1; const originalSetTimeout = global.setTimeout; const originalClearTimeout = global.clearTimeout; global.setTimeout = (callback, delay) => { const id = timerId++; timers.set(id, { callback, delay, type: 'timeout' }); return id; }; global.clearTimeout = (id) => { timers.delete(id); }; return { tick: (ms) => { for (const [id, timer] of timers.entries()) { if (timer.delay <= ms) { timer.callback(); timers.delete(id); } } }, restore: () => { global.setTimeout = originalSetTimeout; global.clearTimeout = originalClearTimeout; timers.clear(); } }; }