import { test, describe, beforeEach, afterEach } from 'node:test'; import { strict as assert } from 'node:assert'; import { createMockDOM, cleanupMockDOM, createLogCapture } from '../utils/test-helpers.js'; import { readFileSync } from 'fs'; import path from 'path'; // Test d'intégration pour le système de navigation describe('Système de Navigation - Tests d\'Intégration', () => { let AppNavigation; let logCapture; let navigationEvents = []; beforeEach(() => { createMockDOM(); logCapture = createLogCapture(); navigationEvents = []; // Mock plus complet pour DOM global.document = { ...global.document, getElementById: (id) => { const elements = { 'app': { style: {}, innerHTML: '', classList: { add: () => {}, remove: () => {} } }, 'home-page': { style: {}, innerHTML: '', classList: { add: () => {}, remove: () => {} } }, 'games-page': { style: {}, innerHTML: '', classList: { add: () => {}, remove: () => {} } }, 'levels-page': { style: {}, innerHTML: '', classList: { add: () => {}, remove: () => {} } }, 'game-page': { style: {}, innerHTML: '', classList: { add: () => {}, remove: () => {} } }, 'breadcrumb': { innerHTML: '', style: {} }, 'network-status': { textContent: '', className: '', style: {} } }; return elements[id] || null; }, querySelectorAll: () => [], addEventListener: (event, handler) => { global.document[`_${event}_handler`] = handler; } }; // Mock window avec navigation global.window = { ...global.window, location: { protocol: 'http:', hostname: 'localhost', port: '8080', search: '', href: 'http://localhost:8080/', assign: (url) => { global.window.location.href = url; global.window.location.search = url.includes('?') ? url.split('?')[1] : ''; navigationEvents.push({ type: 'assign', url }); } }, history: { pushState: (state, title, url) => { global.window.location.href = url; global.window.location.search = url.includes('?') ? url.split('?')[1] : ''; navigationEvents.push({ type: 'pushState', state, title, url }); }, replaceState: (state, title, url) => { global.window.location.href = url; global.window.location.search = url.includes('?') ? url.split('?')[1] : ''; navigationEvents.push({ type: 'replaceState', state, title, url }); } }, addEventListener: (event, handler) => { global.window[`_${event}_handler`] = handler; }, dispatchEvent: (event) => { if (event.type === 'popstate' && global.window._popstate_handler) { global.window._popstate_handler(event); } } }; // Mock URLSearchParams global.URLSearchParams = class { constructor(search) { this.params = new Map(); if (search) { search.split('&').forEach(param => { const [key, value] = param.split('='); if (key && value) { this.params.set(decodeURIComponent(key), decodeURIComponent(value)); } }); } } get(key) { return this.params.get(key); } set(key, value) { this.params.set(key, value); } toString() { const pairs = []; for (const [key, value] of this.params) { pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`); } return pairs.join('&'); } }; // Charger AppNavigation const navPath = path.resolve(process.cwd(), 'js/core/navigation.js'); const code = readFileSync(navPath, 'utf8'); const testCode = code .replace(/window\./g, 'global.') .replace(/typeof window !== 'undefined'/g, 'true'); eval(testCode); AppNavigation = global.AppNavigation; }); afterEach(() => { logCapture.restore(); cleanupMockDOM(); }); describe('Initialisation et configuration', () => { test('devrait créer une instance AppNavigation', () => { const nav = new AppNavigation(); assert.ok(nav instanceof AppNavigation); assert.ok(Array.isArray(nav.navigationHistory)); assert.equal(nav.navigationHistory.length, 0); }); test('devrait charger la configuration par défaut', () => { const nav = new AppNavigation(); const config = nav.getDefaultConfig(); assert.ok(config); assert.ok(config.games); assert.ok(config.content); assert.ok(config.ui); }); }); describe('Analyse d\'URL et paramètres', () => { test('parseURLParams devrait extraire les paramètres correctement', () => { const nav = new AppNavigation(); global.window.location.search = '?page=games&game=whack&content=sbs8'; const params = nav.parseURLParams(); assert.equal(params.page, 'games'); assert.equal(params.game, 'whack'); assert.equal(params.content, 'sbs8'); }); test('parseURLParams devrait retourner objet vide si pas de paramètres', () => { const nav = new AppNavigation(); global.window.location.search = ''; const params = nav.parseURLParams(); assert.equal(Object.keys(params).length, 0); }); test('getCurrentRoute devrait identifier la route actuelle', () => { const nav = new AppNavigation(); // Test route home global.window.location.search = ''; assert.equal(nav.getCurrentRoute(), 'home'); // Test route games global.window.location.search = '?page=games'; assert.equal(nav.getCurrentRoute(), 'games'); // Test route play global.window.location.search = '?page=play&game=whack&content=sbs8'; assert.equal(nav.getCurrentRoute(), 'play'); }); }); describe('Navigation et routage', () => { test('navigateTo devrait mettre à jour l\'URL et l\'historique', () => { const nav = new AppNavigation(); nav.navigateTo('games', { category: 'vocabulary' }); assert.equal(navigationEvents.length, 1); assert.equal(navigationEvents[0].type, 'pushState'); assert.ok(navigationEvents[0].url.includes('page=games')); assert.ok(navigationEvents[0].url.includes('category=vocabulary')); assert.equal(nav.navigationHistory.length, 1); assert.equal(nav.navigationHistory[0].page, 'games'); }); test('navigateToGame devrait créer l\'URL correcte pour un jeu', () => { const nav = new AppNavigation(); nav.navigateToGame('whack-a-mole', 'sbs-content'); const lastEvent = navigationEvents[navigationEvents.length - 1]; assert.ok(lastEvent.url.includes('page=play')); assert.ok(lastEvent.url.includes('game=whack-a-mole')); assert.ok(lastEvent.url.includes('content=sbs-content')); }); test('goBack devrait revenir à la page précédente', () => { const nav = new AppNavigation(); // Naviguer vers quelques pages nav.navigateTo('games'); nav.navigateTo('levels', { game: 'whack' }); nav.navigateTo('play', { game: 'whack', content: 'sbs8' }); assert.equal(nav.navigationHistory.length, 3); // Revenir en arrière nav.goBack(); assert.equal(nav.navigationHistory.length, 2); const lastEvent = navigationEvents[navigationEvents.length - 1]; assert.ok(lastEvent.url.includes('page=levels')); }); test('handlePopState devrait gérer les événements navigateur', () => { const nav = new AppNavigation(); // Simuler un événement popstate const mockEvent = { state: { page: 'games', timestamp: Date.now() } }; nav.handlePopState(mockEvent); // Devrait déclencher une mise à jour de la page const logs = logCapture.getLogs(); assert.ok(logs.some(log => log.message.includes('Navigation'))); }); }); describe('Gestion des pages', () => { test('showPage devrait afficher la page correcte', () => { const nav = new AppNavigation(); nav.showPage('games'); // Vérifier que les éléments DOM sont mis à jour const gamesPage = global.document.getElementById('games-page'); const homePage = global.document.getElementById('home-page'); // Les styles devraient être mis à jour pour afficher/masquer les pages assert.ok(gamesPage); // Should exist assert.ok(homePage); // Should exist }); test('updateBreadcrumb devrait mettre à jour le fil d\'Ariane', () => { const nav = new AppNavigation(); nav.updateBreadcrumb(['Accueil', 'Jeux', 'Whack-a-Mole']); const breadcrumb = global.document.getElementById('breadcrumb'); assert.ok(breadcrumb.innerHTML.length > 0); }); }); describe('État de connectivité', () => { test('updateNetworkStatus devrait mettre à jour l\'indicateur réseau', () => { const nav = new AppNavigation(); nav.updateNetworkStatus('online'); const networkStatus = global.document.getElementById('network-status'); assert.ok(networkStatus.className.includes('online') || networkStatus.textContent.includes('online')); }); test('devrait gérer différents états de réseau', () => { const nav = new AppNavigation(); const states = ['online', 'offline', 'connecting']; states.forEach(state => { nav.updateNetworkStatus(state); const networkStatus = global.document.getElementById('network-status'); assert.ok(networkStatus.className.includes(state) || networkStatus.textContent); }); }); }); describe('Validation de routes', () => { test('isValidRoute devrait valider les routes connues', () => { const nav = new AppNavigation(); assert.equal(nav.isValidRoute('home'), true); assert.equal(nav.isValidRoute('games'), true); assert.equal(nav.isValidRoute('levels'), true); assert.equal(nav.isValidRoute('play'), true); assert.equal(nav.isValidRoute('invalid-route'), false); }); test('validateParams devrait valider les paramètres requis', () => { const nav = new AppNavigation(); // Route play nécessite game et content const validParams = { page: 'play', game: 'whack', content: 'sbs8' }; const invalidParams1 = { page: 'play', game: 'whack' }; // manque content const invalidParams2 = { page: 'play', content: 'sbs8' }; // manque game assert.equal(nav.validateParams(validParams), true); assert.equal(nav.validateParams(invalidParams1), false); assert.equal(nav.validateParams(invalidParams2), false); }); }); describe('Intégration avec le système de jeu', () => { test('devrait intégrer avec GameLoader pour charger les jeux', () => { const nav = new AppNavigation(); // Mock GameLoader global.GameLoader = class { async loadGame(gameType, contentType, container) { return { gameType, contentType, started: false, start: function() { this.started = true; } }; } }; // Simuler une navigation vers un jeu global.window.location.search = '?page=play&game=whack&content=sbs8'; // L'initialisation devrait déclencher le chargement du jeu nav.init(); const logs = logCapture.getLogs(); assert.ok(logs.some(log => log.message.includes('Navigation') || log.message.includes('Initialisation'))); }); }); describe('Gestion des erreurs de navigation', () => { test('devrait gérer les URLs invalides gracieusement', () => { const nav = new AppNavigation(); global.window.location.search = '?page=invalid¶m=malformed%'; try { nav.init(); // Ne devrait pas lever d'erreur assert.ok(true); } catch (error) { assert.fail(`Navigation should handle invalid URLs gracefully: ${error.message}`); } }); test('devrait rediriger vers home si route invalide', () => { const nav = new AppNavigation(); nav.navigateTo('invalid-page'); // Devrait rediriger vers home const lastEvent = navigationEvents[navigationEvents.length - 1]; assert.ok(lastEvent.url.includes('page=home') || !lastEvent.url.includes('page=')); }); }); describe('Historique de navigation', () => { test('devrait maintenir un historique de navigation', () => { const nav = new AppNavigation(); nav.navigateTo('games'); nav.navigateTo('levels', { game: 'whack' }); nav.navigateTo('play', { game: 'whack', content: 'sbs8' }); assert.equal(nav.navigationHistory.length, 3); const history = nav.getNavigationHistory(); assert.equal(history.length, 3); assert.equal(history[0].page, 'games'); assert.equal(history[1].page, 'levels'); assert.equal(history[2].page, 'play'); }); test('devrait limiter la taille de l\'historique', () => { const nav = new AppNavigation(); // Naviguer vers de nombreuses pages for (let i = 0; i < 25; i++) { nav.navigateTo('games', { test: i }); } // L'historique devrait être limité assert.ok(nav.navigationHistory.length <= 20); }); }); describe('Événements personnalisés', () => { test('devrait émettre des événements de navigation', () => { const nav = new AppNavigation(); let eventReceived = false; global.window.addEventListener = (event, handler) => { if (event === 'navigationChange') { eventReceived = true; } }; nav.navigateTo('games'); // L'événement devrait être émis (ou préparé pour émission) assert.ok(navigationEvents.length > 0); }); }); });