From a1af7a775fed964d22d0760824c78601072b2b11 Mon Sep 17 00:00:00 2001 From: Isaries Date: Thu, 8 Oct 2026 14:37:13 +0800 Subject: [PATCH] fix(ProjectService): Empty lesson that transitions to itself overflows the stack getLessonPaths() followed an empty lesson's transitions without checking the path so far, so a lesson that transitions to itself recursed until the stack overflowed. Parsing runs whenever a unit is opened for authoring or preview, so one bad transition made the whole unit unopenable, including the authoring view needed to repair it. Steps already end the path on a loop; do the same for empty lessons. --- src/app/services/projectService.spec.ts | 19 +++++++++++++++++++ src/assets/wise5/services/projectService.ts | 4 ++++ 2 files changed, 23 insertions(+) diff --git a/src/app/services/projectService.spec.ts b/src/app/services/projectService.spec.ts index 9083c3302b0..ce46fc48886 100644 --- a/src/app/services/projectService.spec.ts +++ b/src/app/services/projectService.spec.ts @@ -354,6 +354,25 @@ function getAllPaths() { expect(allPaths2.length).toEqual(1); expect(allPaths2[0]).toEqual(['node5', 'node6', 'node7', 'node8']); }); + it('should end the path at an empty lesson that transitions to itself', () => { + const project = copy(twoStepsProjectJSON); + project.nodes.find((node) => node.id === 'group0').ids.push('group3'); + project.nodes.find((node) => node.id === 'group1').transitionLogic = { + transitions: [{ to: 'group3' }] + }; + project.nodes.push({ + id: 'group3', + type: 'group', + title: 'Empty Lesson', + startId: '', + ids: [], + transitionLogic: { transitions: [{ to: 'group3' }] } + }); + service.setProject(project); + expect(service.getAllPaths([], service.getStartNodeId())).toEqual([ + ['node1', 'node2', 'group3'] + ]); + }); }); } diff --git a/src/assets/wise5/services/projectService.ts b/src/assets/wise5/services/projectService.ts index 2470e3fb0c8..0875809b4bb 100644 --- a/src/assets/wise5/services/projectService.ts +++ b/src/assets/wise5/services/projectService.ts @@ -1039,6 +1039,10 @@ export class ProjectService { const transitions = this.getTransitionsByFromNodeId(groupNode.id); if (transitions.length > 0) { for (const transition of transitions) { + if (pathSoFar.includes(transition.to)) { + allPaths.push([groupId]); + continue; + } const allPathsFromToNode = this.getAllPaths(pathSoFar, transition.to, includeGroups); for (const tempPath of allPathsFromToNode) { tempPath.unshift(groupId);