import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { LessonEngine } from "../../src/impl/LessonEngine.js"; import { PathManager } from "../../src/impl/PathManager.js"; describe("LessonEngine + PathManager Integration", () => { let lessonEngine; let pathManager; let mockModules; let mockPaths; beforeEach(() => { // Clear localStorage before each test localStorage.clear(); // Create mock modules mockModules = [ { id: "module-1", title: "Module 1", mode: "css", lessons: [ { id: "lesson-1-1", title: "Lesson 1.1", task: "Test task 1", initialCode: "", validations: [{ type: "contains", value: "color: red" }] }, { id: "lesson-1-2", title: "Lesson 1.2", task: "Test task 2", initialCode: "", validations: [{ type: "contains", value: "color: blue" }] } ] }, { id: "module-2", title: "Module 2", mode: "css", lessons: [ { id: "lesson-2-1", title: "Lesson 2.1", task: "Test task 3", initialCode: "", validations: [{ type: "contains", value: "color: green" }] }, { id: "lesson-2-2", title: "Lesson 2.2", task: "Test task 4", initialCode: "", validations: [{ type: "contains", value: "color: yellow" }] } ] }, { id: "module-3", title: "Module 3", mode: "css", lessons: [ { id: "lesson-3-1", title: "Lesson 3.1", task: "Test task 5", initialCode: "", validations: [{ type: "contains", value: "color: orange" }] } ] } ]; // Create mock paths mockPaths = [ { id: "path-1", title: "Test Path 1", goal: "Learn basics", estimatedTime: 60, difficulty: "beginner", modules: mockModules // Path includes all modules }, { id: "path-2", title: "Test Path 2", goal: "Advanced concepts", estimatedTime: 90, difficulty: "intermediate", modules: [mockModules[1], mockModules[2]] // Only modules 2 and 3 } ]; // Initialize LessonEngine and PathManager lessonEngine = new LessonEngine(); lessonEngine.setModules(mockModules); pathManager = new PathManager(); pathManager.setPaths(mockPaths); // Connect PathManager to LessonEngine lessonEngine.setPathManager(pathManager); }); afterEach(() => { localStorage.clear(); }); describe("setPathManager()", () => { it("should set the PathManager instance", () => { const engine = new LessonEngine(); const pm = new PathManager(); expect(engine.pathManager).toBeNull(); engine.setPathManager(pm); expect(engine.pathManager).toBe(pm); }); }); describe("nextLesson() with no active path", () => { it("should follow normal module order when no path is active", () => { lessonEngine.setModule(mockModules[0]); lessonEngine.setLessonByIndex(0); expect(lessonEngine.currentLessonIndex).toBe(0); expect(lessonEngine.currentModule.id).toBe("module-1"); // Move to next lesson in same module lessonEngine.nextLesson(); expect(lessonEngine.currentLessonIndex).toBe(1); expect(lessonEngine.currentModule.id).toBe("module-1"); // Move to next module's first lesson lessonEngine.nextLesson(); expect(lessonEngine.currentLessonIndex).toBe(0); expect(lessonEngine.currentModule.id).toBe("module-2"); }); }); describe("nextLesson() with active path", () => { it("should follow path order when path is active", () => { // Start path-2 which includes only module-2 and module-3 pathManager.startPath("path-2"); // Start at first lesson of path (module-2, lesson 0) const firstLesson = pathManager.getNextLesson("path-2"); expect(firstLesson).toEqual({ moduleId: "module-2", lessonIndex: 0 }); lessonEngine.setModuleById(firstLesson.moduleId); lessonEngine.setLessonByIndex(firstLesson.lessonIndex); expect(lessonEngine.currentModule.id).toBe("module-2"); expect(lessonEngine.currentLessonIndex).toBe(0); // Mark first lesson complete pathManager.markLessonCompleted("module-2", 0); // Next lesson should be module-2, lesson 1 lessonEngine.nextLesson(); expect(lessonEngine.currentModule.id).toBe("module-2"); expect(lessonEngine.currentLessonIndex).toBe(1); }); it("should navigate across modules within the path", () => { pathManager.startPath("path-2"); // Start at module-2, lesson 0 lessonEngine.setModuleById("module-2"); lessonEngine.setLessonByIndex(0); // Complete both lessons in module-2 pathManager.markLessonCompleted("module-2", 0); pathManager.markLessonCompleted("module-2", 1); // Next lesson should jump to module-3 (skipping module-1 which isn't in path) lessonEngine.nextLesson(); expect(lessonEngine.currentModule.id).toBe("module-3"); expect(lessonEngine.currentLessonIndex).toBe(0); }); it("should return false when path is complete", () => { pathManager.startPath("path-2"); // Complete all lessons in path-2 pathManager.markLessonCompleted("module-2", 0); pathManager.markLessonCompleted("module-2", 1); pathManager.markLessonCompleted("module-3", 0); // Set to last lesson lessonEngine.setModuleById("module-3"); lessonEngine.setLessonByIndex(0); // Should return false as there's no next lesson in the path const result = lessonEngine.nextLesson(); expect(result).toBe(false); }); }); describe("validateCode() with active path", () => { it("should mark lesson complete in both LessonEngine and PathManager", () => { pathManager.startPath("path-1"); lessonEngine.setModule(mockModules[0]); lessonEngine.setLessonByIndex(0); // Lesson should not be completed initially expect(lessonEngine.isCurrentLessonCompleted()).toBe(false); expect(pathManager.isLessonCompleted("module-1", 0)).toBe(false); // Apply valid code lessonEngine.applyUserCode("color: red"); const result = lessonEngine.validateCode(); expect(result.isValid).toBe(true); // Both should mark it as completed expect(lessonEngine.isCurrentLessonCompleted()).toBe(true); expect(pathManager.isLessonCompleted("module-1", 0)).toBe(true); }); it("should not mark lesson complete in PathManager if validation fails", () => { pathManager.startPath("path-1"); lessonEngine.setModule(mockModules[0]); lessonEngine.setLessonByIndex(0); // Apply invalid code lessonEngine.applyUserCode("color: wrong"); const result = lessonEngine.validateCode(); expect(result.isValid).toBe(false); // Neither should mark it as completed expect(lessonEngine.isCurrentLessonCompleted()).toBe(false); expect(pathManager.isLessonCompleted("module-1", 0)).toBe(false); }); it("should work normally without active path", () => { // No path started lessonEngine.setModule(mockModules[0]); lessonEngine.setLessonByIndex(0); lessonEngine.applyUserCode("color: red"); const result = lessonEngine.validateCode(); expect(result.isValid).toBe(true); // Only LessonEngine should track completion expect(lessonEngine.isCurrentLessonCompleted()).toBe(true); // PathManager doesn't track without active path expect(pathManager.isLessonCompleted("module-1", 0)).toBe(false); }); }); describe("Path-aware navigation workflow", () => { it("should guide user through complete path workflow", () => { // Start a path pathManager.startPath("path-2"); expect(pathManager.getActivePath().id).toBe("path-2"); // Get first lesson and navigate to it const firstLesson = pathManager.getNextLesson("path-2"); lessonEngine.setModuleById(firstLesson.moduleId); lessonEngine.setLessonByIndex(firstLesson.lessonIndex); expect(lessonEngine.currentModule.id).toBe("module-2"); expect(lessonEngine.currentLessonIndex).toBe(0); // Complete first lesson lessonEngine.applyUserCode("color: green"); lessonEngine.validateCode(); // Navigate to next lesson using path order lessonEngine.nextLesson(); expect(lessonEngine.currentModule.id).toBe("module-2"); expect(lessonEngine.currentLessonIndex).toBe(1); // Complete second lesson lessonEngine.applyUserCode("color: yellow"); lessonEngine.validateCode(); // Navigate to next lesson (should cross to module-3) lessonEngine.nextLesson(); expect(lessonEngine.currentModule.id).toBe("module-3"); expect(lessonEngine.currentLessonIndex).toBe(0); // Complete final lesson lessonEngine.applyUserCode("color: orange"); lessonEngine.validateCode(); // Check path completion expect(pathManager.isPathComplete("path-2")).toBe(true); // No more lessons const result = lessonEngine.nextLesson(); expect(result).toBe(false); }); }); describe("PathManager integration without setting PathManager", () => { it("should work normally when PathManager is not set", () => { const engine = new LessonEngine(); engine.setModules(mockModules); expect(engine.pathManager).toBeNull(); // Should navigate normally engine.setModule(mockModules[0]); engine.setLessonByIndex(0); engine.nextLesson(); expect(engine.currentModule.id).toBe("module-1"); expect(engine.currentLessonIndex).toBe(1); // Validation should work engine.applyUserCode("color: blue"); const result = engine.validateCode(); expect(result.isValid).toBe(true); }); }); });