import { describe, test, expect, vi, beforeEach } from "vitest"; import { loadModules, getModuleById, loadModuleFromUrl, addCustomModule, loadLearningPaths } from "../../src/config/lessons.js"; describe("Lessons Config Module", () => { describe("loadModules", () => { test("should return an array of modules", async () => { const modules = await loadModules(); expect(Array.isArray(modules)).toBe(true); expect(modules.length).toBeGreaterThanOrEqual(6); // Check if modules have the right structure const moduleIds = modules.map((m) => m.id); // HTML modules expect(moduleIds).toContain("html-elements"); expect(moduleIds).toContain("html-forms-basic"); expect(moduleIds).toContain("html-forms-validation"); // CSS modules expect(moduleIds).toContain("css-basic-selectors"); expect(moduleIds).toContain("box-model"); expect(moduleIds).toContain("flexbox"); }); test("should have mode set on each lesson", async () => { const modules = await loadModules(); modules.forEach((module) => { module.lessons.forEach((lesson) => { expect(lesson.mode).toBeDefined(); expect(["html", "css", "tailwind"]).toContain(lesson.mode); }); }); }); }); describe("getModuleById", () => { test("should return a module by ID", async () => { // Load modules first to populate the module store await loadModules(); const htmlModule = getModuleById("html-elements"); expect(htmlModule).not.toBeNull(); expect(htmlModule.id).toBe("html-elements"); expect(htmlModule.mode).toBe("html"); }); test("should return null for non-existent module ID", async () => { // Load modules first await loadModules(); const nonExistentModule = getModuleById("non-existent"); expect(nonExistentModule).toBeNull(); }); }); describe("loadModuleFromUrl", () => { beforeEach(() => { // Reset fetch mock fetch.mockReset(); }); test("should load a module from a URL", async () => { const mockModule = { id: "remote-module", title: "Remote Module", lessons: [{ title: "Lesson 1", previewHTML: "
Preview
" }] }; // Mock the fetch response fetch.mockResolvedValueOnce({ ok: true, json: async () => mockModule }); const result = await loadModuleFromUrl("https://example.com/module.json"); expect(fetch).toHaveBeenCalledWith("https://example.com/module.json"); expect(result).toEqual(mockModule); }); test("should throw an error for failed fetch", async () => { fetch.mockResolvedValueOnce({ ok: false, status: 404, statusText: "Not Found" }); await expect(loadModuleFromUrl("https://example.com/not-found.json")).rejects.toThrow("Failed to load module: 404 Not Found"); }); test("should validate module structure", async () => { // Missing required fields const invalidModule = { // Missing id title: "Invalid Module" // Missing lessons array }; fetch.mockResolvedValueOnce({ ok: true, json: async () => invalidModule }); await expect(loadModuleFromUrl("https://example.com/invalid.json")).rejects.toThrow('Module config missing "id"'); // Invalid lessons structure const moduleWithInvalidLessons = { id: "invalid-lessons", title: "Invalid Lessons", lessons: [{ /* Missing title */ previewHTML: "
Preview
" }] }; fetch.mockResolvedValueOnce({ ok: true, json: async () => moduleWithInvalidLessons }); await expect(loadModuleFromUrl("https://example.com/invalid-lessons.json")).rejects.toThrow('Lesson 0 missing "title"'); }); }); describe("addCustomModule", () => { test("should add a new module to the store", async () => { // Load modules first to get current count const initialModules = await loadModules(); const initialCount = initialModules.length; const customModule = { id: "custom-module", title: "Custom Module", lessons: [{ title: "Custom Lesson", previewHTML: "
Preview
" }] }; const result = addCustomModule(customModule); expect(result).toBe(true); // Check if module was added const updatedModules = await loadModules(); expect(updatedModules.length).toBe(initialCount + 1); const addedModule = getModuleById("custom-module"); expect(addedModule).not.toBeNull(); expect(addedModule.title).toBe("Custom Module"); }); test("should replace existing module with same ID", async () => { // Add a module first const customModule = { id: "replace-test", title: "Original Module", lessons: [{ title: "Original Lesson", previewHTML: "
Preview
" }] }; addCustomModule(customModule); // Now replace it const replacementModule = { id: "replace-test", title: "Replacement Module", lessons: [{ title: "New Lesson", previewHTML: "
New Preview
" }] }; const result = addCustomModule(replacementModule); expect(result).toBe(true); // Check if module was replaced const updatedModule = getModuleById("replace-test"); expect(updatedModule.title).toBe("Replacement Module"); }); test("should validate module before adding", () => { const invalidModule = { // Missing required fields title: "Invalid Module" }; const result = addCustomModule(invalidModule); expect(result).toBe(false); }); }); describe("loadLearningPaths", () => { test("should return an array of learning paths", () => { const paths = loadLearningPaths(); expect(Array.isArray(paths)).toBe(true); expect(paths.length).toBeGreaterThanOrEqual(4); // Check if paths have the right structure const pathIds = paths.map((p) => p.id); expect(pathIds).toContain("css-fundamentals"); expect(pathIds).toContain("flexbox-master"); expect(pathIds).toContain("html-forms-expert"); expect(pathIds).toContain("css-animations-pro"); }); test("should validate learning paths on load", () => { // This should not throw as paths are valid expect(() => loadLearningPaths()).not.toThrow(); }); test("should resolve module references to actual module objects", () => { const paths = loadLearningPaths(); paths.forEach((path) => { expect(Array.isArray(path.modules)).toBe(true); expect(path.modules.length).toBeGreaterThan(0); // Check that modules are actual objects, not just IDs path.modules.forEach((module) => { expect(typeof module).toBe("object"); expect(module).not.toBeNull(); expect(module.id).toBeDefined(); expect(module.title).toBeDefined(); expect(Array.isArray(module.lessons)).toBe(true); }); }); }); test("should have required fields on each path", () => { const paths = loadLearningPaths(); paths.forEach((path) => { expect(path.id).toBeDefined(); expect(path.title).toBeDefined(); expect(path.goal).toBeDefined(); expect(typeof path.estimatedTime).toBe("number"); expect(path.estimatedTime).toBeGreaterThan(0); expect(["beginner", "intermediate", "advanced"]).toContain(path.difficulty); expect(Array.isArray(path.modules)).toBe(true); expect(path.modules.length).toBeGreaterThan(0); }); }); test("should support different languages", () => { const pathsEN = loadLearningPaths("en"); const pathsDE = loadLearningPaths("de"); expect(Array.isArray(pathsEN)).toBe(true); expect(Array.isArray(pathsDE)).toBe(true); // Both should have the same number of paths (structure is the same) expect(pathsEN.length).toBe(pathsDE.length); // Modules should be resolved for each language pathsEN.forEach((path) => { expect(path.modules.length).toBeGreaterThan(0); }); pathsDE.forEach((path) => { expect(path.modules.length).toBeGreaterThan(0); }); }); test("should handle missing modules gracefully", () => { const paths = loadLearningPaths(); // Should not throw even if some module references can't be resolved // (they are filtered out with a console warning) expect(Array.isArray(paths)).toBe(true); }); }); });