Class_generator/Legacy/tests/unit/game-loader.test.js
StillHammer 38920cc858 Complete architectural rewrite with ultra-modular system
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>
2025-09-22 07:08:39 +08:00

331 lines
12 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 { sampleJSONContent, gameTestData } from '../fixtures/content-samples.js';
import { readFileSync } from 'fs';
import path from 'path';
describe('GameLoader - Tests Unitaires', () => {
let GameLoader;
let logCapture;
beforeEach(() => {
createMockDOM();
logCapture = createLogCapture();
// Mock GameModules avec des jeux de test
global.GameModules = {
WhackAMole: class {
constructor(options) {
this.container = options.container;
this.content = options.content;
this.onScoreUpdate = options.onScoreUpdate;
this.onGameEnd = options.onGameEnd;
}
start() { this.started = true; }
destroy() { this.destroyed = true; }
restart() { this.restarted = true; }
},
MemoryMatch: class {
constructor(options) {
this.container = options.container;
this.content = options.content;
}
start() { this.started = true; }
destroy() { this.destroyed = true; }
},
QuizGame: class {
constructor(options) {
this.container = options.container;
this.content = options.content;
}
start() { this.started = true; }
destroy() { this.destroyed = true; }
}
};
// Mock ContentModules
global.ContentModules = {
TestContent: sampleJSONContent
};
// Charger GameLoader
const loaderPath = path.resolve(process.cwd(), 'js/core/game-loader.js');
const code = readFileSync(loaderPath, 'utf8');
const testCode = code
.replace(/window\./g, 'global.')
.replace(/typeof window !== 'undefined'/g, 'true');
eval(testCode);
GameLoader = global.GameLoader;
});
afterEach(() => {
logCapture.restore();
cleanupMockDOM();
});
describe('Construction et initialisation', () => {
test('devrait créer une instance GameLoader', () => {
const loader = new GameLoader();
assert.ok(loader instanceof GameLoader);
assert.equal(loader.currentGame, null);
assert.equal(loader.currentGameType, null);
});
});
describe('Chargement de jeux', () => {
test('loadGame devrait charger un jeu avec succès', async () => {
const loader = new GameLoader();
const container = { innerHTML: '' };
const scoreCallback = (score) => {};
const endCallback = () => {};
const game = await loader.loadGame(
'whack-a-mole',
'test-content',
container,
scoreCallback,
endCallback
);
assert.ok(game);
assert.ok(game instanceof global.GameModules.WhackAMole);
assert.equal(game.container, container);
assert.equal(game.content, sampleJSONContent);
assert.equal(loader.currentGame, game);
assert.equal(loader.currentGameType, 'whack-a-mole');
});
test('loadGame devrait nettoyer le jeu précédent', async () => {
const loader = new GameLoader();
const container = { innerHTML: '' };
// Charger un premier jeu
const game1 = await loader.loadGame('whack-a-mole', 'test-content', container);
game1.start();
// Charger un second jeu
const game2 = await loader.loadGame('memory-match', 'test-content', container);
assert.equal(game1.destroyed, true);
assert.equal(loader.currentGame, game2);
assert.ok(game2 instanceof global.GameModules.MemoryMatch);
});
test('loadGame devrait échouer si le jeu n\'existe pas', async () => {
const loader = new GameLoader();
const container = { innerHTML: '' };
try {
await loader.loadGame('nonexistent-game', 'test-content', container);
assert.fail('Should have thrown an error');
} catch (error) {
assert.ok(error.message.includes('Jeu non trouvé'));
}
});
test('loadGame devrait échouer si le contenu n\'existe pas', async () => {
const loader = new GameLoader();
const container = { innerHTML: '' };
try {
await loader.loadGame('whack-a-mole', 'nonexistent-content', container);
assert.fail('Should have thrown an error');
} catch (error) {
assert.ok(error.message.includes('Contenu non trouvé'));
}
});
});
describe('Conversion de noms', () => {
test('getGameClassName devrait convertir les noms de jeu', () => {
const loader = new GameLoader();
assert.equal(loader.getGameClassName('whack-a-mole'), 'WhackAMole');
assert.equal(loader.getGameClassName('memory-match'), 'MemoryMatch');
assert.equal(loader.getGameClassName('quiz-game'), 'QuizGame');
assert.equal(loader.getGameClassName('fill-the-blank'), 'FillTheBlank');
});
test('getContentModuleName devrait convertir les noms de contenu', () => {
const loader = new GameLoader();
assert.equal(loader.getContentModuleName('test-content'), 'TestContent');
assert.equal(loader.getContentModuleName('sbs-level-7-8-new'), 'SBSLevel78New');
assert.equal(loader.getContentModuleName('english-class-demo'), 'EnglishClassDemo');
});
test('toPascalCase devrait convertir correctement', () => {
const loader = new GameLoader();
assert.equal(loader.toPascalCase('hello-world'), 'HelloWorld');
assert.equal(loader.toPascalCase('test-file-name'), 'TestFileName');
assert.equal(loader.toPascalCase('simple'), 'Simple');
assert.equal(loader.toPascalCase(''), '');
});
});
describe('Gestion du cycle de vie des jeux', () => {
test('cleanup devrait détruire le jeu actuel', () => {
const loader = new GameLoader();
const mockGame = {
destroy: () => { mockGame.destroyed = true; },
destroyed: false
};
loader.currentGame = mockGame;
loader.currentGameType = 'test-game';
loader.cleanup();
assert.equal(mockGame.destroyed, true);
assert.equal(loader.currentGame, null);
assert.equal(loader.currentGameType, null);
});
test('cleanup devrait être sûr si pas de jeu actuel', () => {
const loader = new GameLoader();
// Ne devrait pas lever d'erreur
loader.cleanup();
assert.equal(loader.currentGame, null);
assert.equal(loader.currentGameType, null);
});
test('restartCurrentGame devrait redémarrer le jeu actuel', () => {
const loader = new GameLoader();
const mockGame = {
restart: () => { mockGame.restarted = true; },
restarted: false
};
loader.currentGame = mockGame;
loader.restartCurrentGame();
assert.equal(mockGame.restarted, true);
});
test('restartCurrentGame devrait échouer si pas de jeu actuel', () => {
const loader = new GameLoader();
try {
loader.restartCurrentGame();
assert.fail('Should have thrown an error');
} catch (error) {
assert.ok(error.message.includes('Aucun jeu actuel'));
}
});
});
describe('Validation et contraintes', () => {
test('validateGameContent devrait valider le contenu minimal', () => {
const loader = new GameLoader();
const validContent = { vocabulary: { 'test': 'test' } };
const invalidContent1 = {};
const invalidContent2 = { vocabulary: {} };
const invalidContent3 = null;
assert.doesNotThrow(() => loader.validateGameContent(validContent, 'test-game'));
assert.throws(() => loader.validateGameContent(invalidContent1, 'test-game'));
assert.throws(() => loader.validateGameContent(invalidContent2, 'test-game'));
assert.throws(() => loader.validateGameContent(invalidContent3, 'test-game'));
});
test('checkGameRequirements devrait vérifier les exigences spécifiques', () => {
const loader = new GameLoader();
// Contenu avec vocabulaire suffisant
const goodContent = {
vocabulary: gameTestData.whackAMole.vocabulary
};
// Contenu avec vocabulaire insuffisant
const poorContent = {
vocabulary: { 'only': 'one' }
};
assert.doesNotThrow(() => loader.checkGameRequirements(goodContent, 'whack-a-mole'));
assert.throws(() => loader.checkGameRequirements(poorContent, 'whack-a-mole'));
});
});
describe('Gestion des erreurs', () => {
test('devrait logger les erreurs de chargement', async () => {
const loader = new GameLoader();
const container = { innerHTML: '' };
try {
await loader.loadGame('nonexistent-game', 'test-content', container);
} catch (error) {
// Expected
}
const errorLogs = logCapture.getLogs('ERROR');
assert.ok(errorLogs.length > 0);
assert.ok(errorLogs.some(log => log.message.includes('Erreur lors du chargement')));
});
test('devrait gérer les erreurs de construction de jeu', async () => {
const loader = new GameLoader();
const container = { innerHTML: '' };
// Mock un jeu qui lève une erreur à la construction
global.GameModules.BrokenGame = class {
constructor() {
throw new Error('Construction failed');
}
};
try {
await loader.loadGame('broken-game', 'test-content', container);
assert.fail('Should have thrown an error');
} catch (error) {
assert.ok(error.message.includes('Erreur lors de la création'));
}
});
});
describe('État et informations', () => {
test('getCurrentGameInfo devrait retourner les informations du jeu actuel', () => {
const loader = new GameLoader();
const mockGame = {};
loader.currentGame = mockGame;
loader.currentGameType = 'test-game';
const info = loader.getCurrentGameInfo();
assert.equal(info.gameType, 'test-game');
assert.equal(info.game, mockGame);
assert.equal(typeof info.loadTime, 'number');
});
test('getCurrentGameInfo devrait retourner null si pas de jeu', () => {
const loader = new GameLoader();
const info = loader.getCurrentGameInfo();
assert.equal(info, null);
});
test('isGameLoaded devrait retourner l\'état correct', () => {
const loader = new GameLoader();
assert.equal(loader.isGameLoaded(), false);
loader.currentGame = {};
assert.equal(loader.isGameLoaded(), true);
loader.currentGame = null;
assert.equal(loader.isGameLoaded(), false);
});
});
});