feat: add Guided Learning Paths feature
Implement PathManager to orchestrate multi-module learning journeys: - Add PathManager class with start/pause/resume functionality - Create learning-paths.json config with CSS Fundamentals path - Integrate path progress tracking with LessonEngine - Add path selection UI to homepage and navigation - Include JSON schema for learning path validation - Add comprehensive test suite for PathManager
This commit is contained in:
567
tests/unit/pathManager.test.js
Normal file
567
tests/unit/pathManager.test.js
Normal file
@@ -0,0 +1,567 @@
|
||||
/**
|
||||
* Comprehensive unit tests for PathManager
|
||||
* Tests: path loading, progress tracking, next lesson calculation,
|
||||
* localStorage persistence, and edge cases
|
||||
*/
|
||||
import { describe, it, expect, beforeEach } from "vitest";
|
||||
import { PathManager } from "../../src/impl/PathManager.js";
|
||||
|
||||
describe("PathManager", () => {
|
||||
let pathManager;
|
||||
let mockPaths;
|
||||
|
||||
beforeEach(() => {
|
||||
// Clear localStorage before each test
|
||||
localStorage.clear();
|
||||
|
||||
// Create comprehensive mock paths
|
||||
mockPaths = [
|
||||
{
|
||||
id: "css-fundamentals",
|
||||
title: "CSS Fundamentals",
|
||||
goal: "Master CSS basics",
|
||||
difficulty: "beginner",
|
||||
estimatedTime: 60,
|
||||
modules: [
|
||||
{
|
||||
id: "basic-selectors",
|
||||
lessons: [{}, {}, {}] // 3 lessons
|
||||
},
|
||||
{
|
||||
id: "box-model",
|
||||
lessons: [{}, {}] // 2 lessons
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "flexbox-master",
|
||||
title: "Flexbox Master",
|
||||
goal: "Become a Flexbox expert",
|
||||
difficulty: "intermediate",
|
||||
estimatedTime: 90,
|
||||
modules: [
|
||||
{
|
||||
id: "flex-basics",
|
||||
lessons: [{}, {}] // 2 lessons
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "empty-path",
|
||||
title: "Empty Path",
|
||||
goal: "Path with no modules",
|
||||
difficulty: "beginner",
|
||||
estimatedTime: 0,
|
||||
modules: []
|
||||
}
|
||||
];
|
||||
|
||||
// Create fresh PathManager instance
|
||||
pathManager = new PathManager();
|
||||
pathManager.setPaths(mockPaths);
|
||||
});
|
||||
|
||||
describe("Path Loading", () => {
|
||||
it("should initialize with empty state", () => {
|
||||
const newPathManager = new PathManager();
|
||||
expect(newPathManager.paths).toEqual([]);
|
||||
expect(newPathManager.activePathId).toBeNull();
|
||||
expect(newPathManager.pathProgress).toEqual({});
|
||||
});
|
||||
|
||||
it("should set paths using setPaths()", () => {
|
||||
const newPathManager = new PathManager();
|
||||
newPathManager.setPaths(mockPaths);
|
||||
expect(newPathManager.paths).toEqual(mockPaths);
|
||||
expect(newPathManager.paths.length).toBe(3);
|
||||
});
|
||||
|
||||
it("should handle empty paths array", () => {
|
||||
pathManager.setPaths([]);
|
||||
expect(pathManager.paths).toEqual([]);
|
||||
expect(pathManager.getActivePath()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Progress Tracking - getPathProgress()", () => {
|
||||
it("should return null for invalid path ID", () => {
|
||||
const progress = pathManager.getPathProgress("non-existent");
|
||||
expect(progress).toBeNull();
|
||||
});
|
||||
|
||||
it("should return default progress for path that hasn't been started", () => {
|
||||
const progress = pathManager.getPathProgress("css-fundamentals");
|
||||
expect(progress).toEqual({
|
||||
pathId: "css-fundamentals",
|
||||
completedLessons: [],
|
||||
completedCount: 0,
|
||||
totalLessons: 5, // 3 + 2 from modules
|
||||
percentComplete: 0,
|
||||
startTimestamp: null,
|
||||
lastActivityTimestamp: null,
|
||||
isStarted: false,
|
||||
isComplete: false
|
||||
});
|
||||
});
|
||||
|
||||
it("should calculate total lessons correctly across multiple modules", () => {
|
||||
const progress = pathManager.getPathProgress("css-fundamentals");
|
||||
expect(progress.totalLessons).toBe(5); // 3 + 2
|
||||
});
|
||||
|
||||
it("should return accurate progress after starting a path", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
const progress = pathManager.getPathProgress("css-fundamentals");
|
||||
|
||||
expect(progress.isStarted).toBe(true);
|
||||
expect(progress.startTimestamp).not.toBeNull();
|
||||
expect(progress.lastActivityTimestamp).not.toBeNull();
|
||||
expect(progress.completedCount).toBe(0);
|
||||
expect(progress.percentComplete).toBe(0);
|
||||
});
|
||||
|
||||
it("should update progress after marking lessons as completed", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
pathManager.markLessonCompleted("basic-selectors", 1);
|
||||
|
||||
const progress = pathManager.getPathProgress("css-fundamentals");
|
||||
expect(progress.completedCount).toBe(2);
|
||||
expect(progress.percentComplete).toBe(40); // 2/5 = 40%
|
||||
expect(progress.completedLessons).toContain("basic-selectors-0");
|
||||
expect(progress.completedLessons).toContain("basic-selectors-1");
|
||||
});
|
||||
|
||||
it("should calculate percentage correctly", () => {
|
||||
pathManager.startPath("flexbox-master");
|
||||
pathManager.markLessonCompleted("flex-basics", 0);
|
||||
|
||||
const progress = pathManager.getPathProgress("flexbox-master");
|
||||
expect(progress.percentComplete).toBe(50); // 1/2 = 50%
|
||||
});
|
||||
|
||||
it("should handle paths with no lessons (empty modules)", () => {
|
||||
const progress = pathManager.getPathProgress("empty-path");
|
||||
expect(progress.totalLessons).toBe(0);
|
||||
expect(progress.percentComplete).toBe(0);
|
||||
expect(progress.isComplete).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Lesson Completion - markLessonCompleted() and isLessonCompleted()", () => {
|
||||
it("should mark a lesson as completed when path is active", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
|
||||
expect(pathManager.isLessonCompleted("basic-selectors", 0)).toBe(true);
|
||||
});
|
||||
|
||||
it("should not mark lesson as completed when no path is active", () => {
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
expect(pathManager.isLessonCompleted("basic-selectors", 0)).toBe(false);
|
||||
});
|
||||
|
||||
it("should not mark the same lesson twice", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
|
||||
const progress = pathManager.getPathProgress("css-fundamentals");
|
||||
expect(progress.completedCount).toBe(1);
|
||||
expect(progress.completedLessons.filter((l) => l === "basic-selectors-0").length).toBe(1);
|
||||
});
|
||||
|
||||
it("should update lastActivityTimestamp when marking lesson completed", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
const progressBefore = pathManager.getPathProgress("css-fundamentals");
|
||||
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
|
||||
const progressAfter = pathManager.getPathProgress("css-fundamentals");
|
||||
expect(progressAfter.lastActivityTimestamp).not.toBe(progressBefore.lastActivityTimestamp);
|
||||
});
|
||||
|
||||
it("should return false for non-completed lessons", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
expect(pathManager.isLessonCompleted("basic-selectors", 0)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when no path is active", () => {
|
||||
expect(pathManager.isLessonCompleted("basic-selectors", 0)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Next Lesson Calculation - getNextLesson()", () => {
|
||||
it("should return null for invalid path ID", () => {
|
||||
const nextLesson = pathManager.getNextLesson("non-existent");
|
||||
expect(nextLesson).toBeNull();
|
||||
});
|
||||
|
||||
it("should return first lesson of first module for unstarted path", () => {
|
||||
const nextLesson = pathManager.getNextLesson("css-fundamentals");
|
||||
expect(nextLesson).toEqual({
|
||||
moduleId: "basic-selectors",
|
||||
lessonIndex: 0
|
||||
});
|
||||
});
|
||||
|
||||
it("should return next incomplete lesson within same module", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
|
||||
const nextLesson = pathManager.getNextLesson("css-fundamentals");
|
||||
expect(nextLesson).toEqual({
|
||||
moduleId: "basic-selectors",
|
||||
lessonIndex: 1
|
||||
});
|
||||
});
|
||||
|
||||
it("should move to next module when current module is completed", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
// Complete all lessons in basic-selectors
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
pathManager.markLessonCompleted("basic-selectors", 1);
|
||||
pathManager.markLessonCompleted("basic-selectors", 2);
|
||||
|
||||
const nextLesson = pathManager.getNextLesson("css-fundamentals");
|
||||
expect(nextLesson).toEqual({
|
||||
moduleId: "box-model",
|
||||
lessonIndex: 0
|
||||
});
|
||||
});
|
||||
|
||||
it("should return null when all lessons are completed", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
// Complete all lessons
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
pathManager.markLessonCompleted("basic-selectors", 1);
|
||||
pathManager.markLessonCompleted("basic-selectors", 2);
|
||||
pathManager.markLessonCompleted("box-model", 0);
|
||||
pathManager.markLessonCompleted("box-model", 1);
|
||||
|
||||
const nextLesson = pathManager.getNextLesson("css-fundamentals");
|
||||
expect(nextLesson).toBeNull();
|
||||
});
|
||||
|
||||
it("should handle paths with no modules", () => {
|
||||
const nextLesson = pathManager.getNextLesson("empty-path");
|
||||
expect(nextLesson).toBeNull();
|
||||
});
|
||||
|
||||
it("should skip modules with no lessons", () => {
|
||||
const pathWithEmptyModule = [
|
||||
{
|
||||
id: "test-path",
|
||||
modules: [
|
||||
{ id: "empty-module" }, // No lessons array
|
||||
{ id: "valid-module", lessons: [{}] }
|
||||
]
|
||||
}
|
||||
];
|
||||
pathManager.setPaths(pathWithEmptyModule);
|
||||
|
||||
const nextLesson = pathManager.getNextLesson("test-path");
|
||||
expect(nextLesson).toEqual({
|
||||
moduleId: "valid-module",
|
||||
lessonIndex: 0
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Path Completion - isPathComplete()", () => {
|
||||
it("should return false for invalid path ID", () => {
|
||||
expect(pathManager.isPathComplete("non-existent")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for unstarted path", () => {
|
||||
expect(pathManager.isPathComplete("css-fundamentals")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for partially completed path", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
expect(pathManager.isPathComplete("css-fundamentals")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true when all lessons are completed", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
// Complete all lessons
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
pathManager.markLessonCompleted("basic-selectors", 1);
|
||||
pathManager.markLessonCompleted("basic-selectors", 2);
|
||||
pathManager.markLessonCompleted("box-model", 0);
|
||||
pathManager.markLessonCompleted("box-model", 1);
|
||||
|
||||
expect(pathManager.isPathComplete("css-fundamentals")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for empty paths", () => {
|
||||
expect(pathManager.isPathComplete("empty-path")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Time Estimation - calculateEstimatedTimeRemaining()", () => {
|
||||
it("should return 0 for invalid path ID", () => {
|
||||
const remaining = pathManager.calculateEstimatedTimeRemaining("non-existent");
|
||||
expect(remaining).toBe(0);
|
||||
});
|
||||
|
||||
it("should return full estimated time for unstarted path", () => {
|
||||
const remaining = pathManager.calculateEstimatedTimeRemaining("css-fundamentals");
|
||||
expect(remaining).toBe(60);
|
||||
});
|
||||
|
||||
it("should calculate remaining time based on completion percentage", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
pathManager.markLessonCompleted("basic-selectors", 1);
|
||||
// 2 out of 5 lessons = 40% complete, so 60% remaining
|
||||
// 60 minutes * 0.6 = 36 minutes
|
||||
|
||||
const remaining = pathManager.calculateEstimatedTimeRemaining("css-fundamentals");
|
||||
expect(remaining).toBe(36);
|
||||
});
|
||||
|
||||
it("should return 0 when path is completed", () => {
|
||||
pathManager.startPath("flexbox-master");
|
||||
pathManager.markLessonCompleted("flex-basics", 0);
|
||||
pathManager.markLessonCompleted("flex-basics", 1);
|
||||
|
||||
const remaining = pathManager.calculateEstimatedTimeRemaining("flexbox-master");
|
||||
expect(remaining).toBe(0);
|
||||
});
|
||||
|
||||
it("should handle 50% completion correctly", () => {
|
||||
pathManager.startPath("flexbox-master");
|
||||
pathManager.markLessonCompleted("flex-basics", 0);
|
||||
// 1 out of 2 lessons = 50% complete
|
||||
// 90 minutes * 0.5 = 45 minutes
|
||||
|
||||
const remaining = pathManager.calculateEstimatedTimeRemaining("flexbox-master");
|
||||
expect(remaining).toBe(45);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Get All Paths With Progress - getAllPathsWithProgress()", () => {
|
||||
it("should return all paths with their progress data", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
|
||||
const allPaths = pathManager.getAllPathsWithProgress();
|
||||
expect(allPaths.length).toBe(3);
|
||||
|
||||
const cssPath = allPaths.find((p) => p.id === "css-fundamentals");
|
||||
expect(cssPath.progress).toBeDefined();
|
||||
expect(cssPath.progress.completedCount).toBe(1);
|
||||
expect(cssPath.progress.isStarted).toBe(true);
|
||||
});
|
||||
|
||||
it("should include progress for all paths even if not started", () => {
|
||||
const allPaths = pathManager.getAllPathsWithProgress();
|
||||
allPaths.forEach((path) => {
|
||||
expect(path.progress).toBeDefined();
|
||||
expect(path.progress.isStarted).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it("should return empty array when no paths are set", () => {
|
||||
pathManager.setPaths([]);
|
||||
const allPaths = pathManager.getAllPathsWithProgress();
|
||||
expect(allPaths).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("LocalStorage Persistence", () => {
|
||||
it("should save progress to localStorage when marking lessons completed", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
|
||||
const saved = JSON.parse(localStorage.getItem("codeCrispies.pathProgress"));
|
||||
expect(saved).not.toBeNull();
|
||||
expect(saved.activePathId).toBe("css-fundamentals");
|
||||
expect(saved.pathProgress["css-fundamentals"].completedLessons).toContain("basic-selectors-0");
|
||||
});
|
||||
|
||||
it("should save timestamp with progress data", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
const saved = JSON.parse(localStorage.getItem("codeCrispies.pathProgress"));
|
||||
expect(saved.timestamp).toBeDefined();
|
||||
expect(saved.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T/); // ISO format
|
||||
});
|
||||
|
||||
it("should load progress from localStorage on initialization", () => {
|
||||
// Manually set localStorage data
|
||||
const progressData = {
|
||||
activePathId: "flexbox-master",
|
||||
pathProgress: {
|
||||
"flexbox-master": {
|
||||
completedLessons: ["flex-basics-0"],
|
||||
startTimestamp: "2024-01-01T00:00:00.000Z",
|
||||
lastActivityTimestamp: "2024-01-01T00:30:00.000Z"
|
||||
}
|
||||
},
|
||||
timestamp: "2024-01-01T00:30:00.000Z"
|
||||
};
|
||||
localStorage.setItem("codeCrispies.pathProgress", JSON.stringify(progressData));
|
||||
|
||||
// Create new PathManager (should load from localStorage)
|
||||
const newPathManager = new PathManager();
|
||||
newPathManager.setPaths(mockPaths);
|
||||
|
||||
expect(newPathManager.activePathId).toBe("flexbox-master");
|
||||
expect(newPathManager.isLessonCompleted("flex-basics", 0)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return metadata when loading progress", () => {
|
||||
const progressData = {
|
||||
activePathId: "css-fundamentals",
|
||||
pathProgress: {},
|
||||
timestamp: "2024-01-01T00:00:00.000Z"
|
||||
};
|
||||
localStorage.setItem("codeCrispies.pathProgress", JSON.stringify(progressData));
|
||||
|
||||
const newPathManager = new PathManager();
|
||||
// loadPathProgress is called in constructor, but we can call it again
|
||||
const metadata = newPathManager.loadPathProgress();
|
||||
|
||||
expect(metadata).not.toBeNull();
|
||||
expect(metadata.activePathId).toBe("css-fundamentals");
|
||||
expect(metadata.timestamp).toBe("2024-01-01T00:00:00.000Z");
|
||||
});
|
||||
|
||||
it("should handle corrupted localStorage data gracefully", () => {
|
||||
localStorage.setItem("codeCrispies.pathProgress", "invalid json {{{");
|
||||
|
||||
// Should not throw
|
||||
const newPathManager = new PathManager();
|
||||
expect(newPathManager.activePathId).toBeNull();
|
||||
expect(newPathManager.pathProgress).toEqual({});
|
||||
});
|
||||
|
||||
it("should handle missing localStorage data", () => {
|
||||
const newPathManager = new PathManager();
|
||||
expect(newPathManager.activePathId).toBeNull();
|
||||
expect(newPathManager.pathProgress).toEqual({});
|
||||
});
|
||||
|
||||
it("should persist multiple paths progress independently", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
pathManager.pausePath();
|
||||
|
||||
pathManager.startPath("flexbox-master");
|
||||
pathManager.markLessonCompleted("flex-basics", 0);
|
||||
|
||||
const saved = JSON.parse(localStorage.getItem("codeCrispies.pathProgress"));
|
||||
expect(saved.pathProgress["css-fundamentals"].completedLessons).toContain("basic-selectors-0");
|
||||
expect(saved.pathProgress["flexbox-master"].completedLessons).toContain("flex-basics-0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Clear Progress - clearProgress()", () => {
|
||||
it("should clear all progress and active state", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
|
||||
pathManager.clearProgress();
|
||||
|
||||
expect(pathManager.activePathId).toBeNull();
|
||||
expect(pathManager.pathProgress).toEqual({});
|
||||
expect(localStorage.getItem("codeCrispies.pathProgress")).toBeNull();
|
||||
});
|
||||
|
||||
it("should remove data from localStorage", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
expect(localStorage.getItem("codeCrispies.pathProgress")).not.toBeNull();
|
||||
|
||||
pathManager.clearProgress();
|
||||
expect(localStorage.getItem("codeCrispies.pathProgress")).toBeNull();
|
||||
});
|
||||
|
||||
it("should allow starting fresh after clearing", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
pathManager.clearProgress();
|
||||
|
||||
pathManager.startPath("flexbox-master");
|
||||
const progress = pathManager.getPathProgress("flexbox-master");
|
||||
expect(progress.isStarted).toBe(true);
|
||||
expect(progress.completedCount).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge Cases", () => {
|
||||
it("should handle paths with null or undefined modules array", () => {
|
||||
const pathWithNullModules = [
|
||||
{
|
||||
id: "null-modules",
|
||||
modules: null,
|
||||
estimatedTime: 60
|
||||
}
|
||||
];
|
||||
pathManager.setPaths(pathWithNullModules);
|
||||
|
||||
// Should not throw
|
||||
expect(() => pathManager.getPathProgress("null-modules")).not.toThrow();
|
||||
});
|
||||
|
||||
it("should handle lessons with special characters in module IDs", () => {
|
||||
const specialPath = [
|
||||
{
|
||||
id: "special-path",
|
||||
modules: [
|
||||
{ id: "module-with-dashes", lessons: [{}] }
|
||||
],
|
||||
estimatedTime: 30
|
||||
}
|
||||
];
|
||||
pathManager.setPaths(specialPath);
|
||||
pathManager.startPath("special-path");
|
||||
pathManager.markLessonCompleted("module-with-dashes", 0);
|
||||
|
||||
expect(pathManager.isLessonCompleted("module-with-dashes", 0)).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle very large lesson indices", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
pathManager.markLessonCompleted("basic-selectors", 999);
|
||||
|
||||
expect(pathManager.isLessonCompleted("basic-selectors", 999)).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle zero estimated time", () => {
|
||||
const remaining = pathManager.calculateEstimatedTimeRemaining("empty-path");
|
||||
expect(remaining).toBe(0);
|
||||
});
|
||||
|
||||
it("should handle path with completed lessons but never formally started", () => {
|
||||
// Manually add progress without starting path
|
||||
pathManager.pathProgress["css-fundamentals"] = {
|
||||
completedLessons: ["basic-selectors-0"],
|
||||
startTimestamp: null,
|
||||
lastActivityTimestamp: null
|
||||
};
|
||||
|
||||
const progress = pathManager.getPathProgress("css-fundamentals");
|
||||
expect(progress.isStarted).toBe(false);
|
||||
expect(progress.completedCount).toBe(1);
|
||||
});
|
||||
|
||||
it("should handle switching between paths multiple times", () => {
|
||||
pathManager.startPath("css-fundamentals");
|
||||
pathManager.markLessonCompleted("basic-selectors", 0);
|
||||
|
||||
pathManager.startPath("flexbox-master");
|
||||
pathManager.markLessonCompleted("flex-basics", 0);
|
||||
|
||||
pathManager.startPath("css-fundamentals");
|
||||
pathManager.markLessonCompleted("basic-selectors", 1);
|
||||
|
||||
const cssProgress = pathManager.getPathProgress("css-fundamentals");
|
||||
const flexProgress = pathManager.getPathProgress("flexbox-master");
|
||||
|
||||
expect(cssProgress.completedCount).toBe(2);
|
||||
expect(flexProgress.completedCount).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user