Class_generator/tests/integration/navigation-system.test.js
StillHammer cb614a439d Add comprehensive test suite with unit tests and integration tests
- Complete test infrastructure with runners, helpers, and fixtures
- Unit tests for core modules: EnvConfig, ContentScanner, GameLoader
- Integration tests for proxy, content loading, and navigation
- Edge case tests covering data corruption, network failures, security
- Stress tests with 100+ concurrent requests and performance monitoring
- Test fixtures with malicious content samples and edge case data
- Comprehensive README with usage instructions and troubleshooting

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-16 11:37:08 +08:00

415 lines
15 KiB
JavaScript

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&param=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);
});
});
});