diff --git a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/testing/ClassroomMonitorTestHelper.ts b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/testing/ClassroomMonitorTestHelper.ts
index 5461b028e51..415f9cbf13f 100644
--- a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/testing/ClassroomMonitorTestHelper.ts
+++ b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/testing/ClassroomMonitorTestHelper.ts
@@ -1,4 +1,4 @@
-import { StudentProgress } from '../../../student-progress/student-progress.component';
+import { StudentProgress } from '../../../student-progress/StudentProgress';
export class ClassroomMonitorTestHelper {
workgroupId1: number = 1;
diff --git a/src/assets/wise5/classroomMonitor/project-location/project-location.component.html b/src/assets/wise5/classroomMonitor/project-location/project-location.component.html
new file mode 100644
index 00000000000..fda70b3369e
--- /dev/null
+++ b/src/assets/wise5/classroomMonitor/project-location/project-location.component.html
@@ -0,0 +1,21 @@
+
;
+ let projectService: ProjectService;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ClassroomMonitorTestingModule, ProjectLocationComponent]
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ProjectLocationComponent);
+ component = fixture.componentInstance;
+ projectService = TestBed.inject(ProjectService);
+ });
+
+ describe('ngOnChanges()', () => {
+ describe('when project has multiple group nodes', () => {
+ beforeEach(() => {
+ spyOn(projectService, 'getOrderedGroupNodes').and.returnValue([
+ { id: 'group1', title: 'Segment 1' },
+ { id: 'group2', title: 'Segment 2' },
+ { id: 'group3', title: 'Segment 3' }
+ ]);
+ spyOn(projectService, 'getParentGroup').and.returnValue({
+ id: 'group2',
+ title: 'Segment 2'
+ });
+ });
+
+ it('should set segments to group nodes', () => {
+ component.studentProgress = new StudentProgress({
+ currentNodeId: 'node2',
+ nodePosition: '2.1',
+ positionAndTitle: '2.1: Step Title'
+ });
+ component.ngOnChanges();
+ expect(component['segments'].length).toBe(3);
+ expect(component['segments'][0].id).toBe('group1');
+ });
+
+ it('should set current segment to parent group of current node', () => {
+ component.studentProgress = new StudentProgress({
+ currentNodeId: 'node2',
+ nodePosition: '2.1',
+ positionAndTitle: '2.1: Step Title'
+ });
+ component.ngOnChanges();
+ expect(component['currentSegment'].id).toBe('group2');
+ expect(projectService.getParentGroup).toHaveBeenCalledWith('node2');
+ });
+ });
+
+ describe('when project has single or no group nodes', () => {
+ beforeEach(() => {
+ spyOn(projectService, 'getOrderedGroupNodes').and.returnValue([
+ { id: 'group1', title: 'Main Group' }
+ ]);
+ spyOn(projectService, 'getNode').and.returnValue({
+ id: 'node2',
+ title: 'Step 2',
+ type: 'node'
+ } as Node);
+ projectService.idToOrder = {
+ node1: { order: 1 },
+ node2: { order: 2 },
+ node3: { order: 3 },
+ group1: { order: 0 }
+ };
+ spyOn(projectService, 'getNodes').and.returnValue([
+ { id: 'group1', type: 'group' },
+ { id: 'node1', type: 'node' },
+ { id: 'node2', type: 'node' },
+ { id: 'node3', type: 'node' }
+ ]);
+ });
+
+ it('should set segments to ordered step nodes', () => {
+ component.studentProgress = new StudentProgress({
+ currentNodeId: 'node2',
+ nodePosition: '2',
+ positionAndTitle: '2: Step Title'
+ });
+ component.ngOnChanges();
+ expect(component['segments'].length).toBe(3);
+ expect(component['segments'][0].id).toBe('node1');
+ expect(component['segments'][1].id).toBe('node2');
+ expect(component['segments'][2].id).toBe('node3');
+ });
+
+ it('should set current segment to current node', () => {
+ component.studentProgress = new StudentProgress({
+ currentNodeId: 'node2',
+ nodePosition: '2',
+ positionAndTitle: '2: Step Title'
+ });
+ component.ngOnChanges();
+ expect(component['currentSegment'].id).toBe('node2');
+ expect(projectService.getNode).toHaveBeenCalledWith('node2');
+ });
+
+ it('should filter out group nodes from segments', () => {
+ component.studentProgress = new StudentProgress({
+ currentNodeId: 'node1',
+ nodePosition: '1',
+ positionAndTitle: '1: First Step'
+ });
+ component.ngOnChanges();
+ const hasGroupNode = component['segments'].some((segment) => segment.type === 'group');
+ expect(hasGroupNode).toBeFalse();
+ });
+
+ it('should sort nodes by order', () => {
+ projectService.idToOrder = {
+ node1: { order: 3 },
+ node2: { order: 1 },
+ node3: { order: 2 },
+ group1: { order: 0 }
+ };
+ component.studentProgress = new StudentProgress({
+ currentNodeId: 'node2',
+ nodePosition: '1',
+ positionAndTitle: '1: Step Title'
+ });
+ component.ngOnChanges();
+ expect(component['segments'][0].id).toBe('node2');
+ expect(component['segments'][1].id).toBe('node3');
+ expect(component['segments'][2].id).toBe('node1');
+ });
+ });
+ });
+
+ describe('template rendering', () => {
+ beforeEach(() => {
+ spyOn(projectService, 'getOrderedGroupNodes').and.returnValue([
+ { id: 'group1', title: 'Segment 1' },
+ { id: 'group2', title: 'Segment 2' },
+ { id: 'group3', title: 'Segment 3' }
+ ]);
+ spyOn(projectService, 'getParentGroup').and.returnValue({ id: 'group2', title: 'Segment 2' });
+ });
+
+ it('should display tooltip with position and title', () => {
+ component.studentProgress = new StudentProgress({
+ currentNodeId: 'node2',
+ nodePosition: '2.1',
+ positionAndTitle: '2.1: Step Title'
+ });
+ component.ngOnChanges();
+ fixture.detectChanges();
+ expect(component.studentProgress.positionAndTitle).toBe('2.1: Step Title');
+ });
+
+ it('should render all segments', () => {
+ component.studentProgress = new StudentProgress({
+ currentNodeId: 'node2',
+ nodePosition: '2.1',
+ positionAndTitle: '2.1: Step Title'
+ });
+ component.ngOnChanges();
+ fixture.detectChanges();
+ const segments = fixture.nativeElement.querySelectorAll('.segment');
+ expect(segments.length).toBe(3);
+ });
+
+ it('should display node position and icon for current segment', () => {
+ component.studentProgress = new StudentProgress({
+ currentNodeId: 'node2',
+ nodePosition: '2.1',
+ positionAndTitle: '2.1: Step Title'
+ });
+ component.ngOnChanges();
+ fixture.detectChanges();
+ const segments = fixture.nativeElement.querySelectorAll('.segment');
+ const activeSegment = segments[1]; // group2 is the second segment
+ expect(activeSegment.textContent.trim()).toContain('2.1');
+ expect(activeSegment.querySelector('mat-icon')).toBeTruthy();
+ expect(activeSegment.querySelector('mat-icon').textContent).toBe('place');
+ });
+
+ it('should apply active style to current segment bar', () => {
+ component.studentProgress = new StudentProgress({
+ currentNodeId: 'node2',
+ nodePosition: '2.1',
+ positionAndTitle: '2.1: Step Title'
+ });
+ component.ngOnChanges();
+ fixture.detectChanges();
+ const segments = fixture.nativeElement.querySelectorAll('.segment');
+ const activeSegmentBar = segments[1].querySelector('.segment-bar');
+ expect(activeSegmentBar.classList.contains('primary-bg')).toBeTrue();
+ });
+
+ it('should not display node position or icon for non-current segments', () => {
+ component.studentProgress = new StudentProgress({
+ currentNodeId: 'node2',
+ nodePosition: '2.1',
+ positionAndTitle: '2.1: Step Title'
+ });
+ component.ngOnChanges();
+ fixture.detectChanges();
+ const segments = fixture.nativeElement.querySelectorAll('.segment');
+ const inactiveSegment = segments[0]; // group1 is the first segment
+ expect(inactiveSegment.querySelector('mat-icon')).toBeFalsy();
+ expect(inactiveSegment.textContent.trim()).not.toContain('2.1');
+ });
+
+ it('should not apply active style to non-current segment bars', () => {
+ component.studentProgress = new StudentProgress({
+ currentNodeId: 'node2',
+ nodePosition: '2.1',
+ positionAndTitle: '2.1: Step Title'
+ });
+ component.ngOnChanges();
+ fixture.detectChanges();
+ const segments = fixture.nativeElement.querySelectorAll('.segment');
+ const inactiveSegmentBar = segments[0].querySelector('.segment-bar');
+ expect(inactiveSegmentBar.classList.contains('primary-bg')).toBeFalse();
+ });
+ });
+});
diff --git a/src/assets/wise5/classroomMonitor/project-location/project-location.component.ts b/src/assets/wise5/classroomMonitor/project-location/project-location.component.ts
new file mode 100644
index 00000000000..069033de763
--- /dev/null
+++ b/src/assets/wise5/classroomMonitor/project-location/project-location.component.ts
@@ -0,0 +1,38 @@
+import { Component, inject, Input } from '@angular/core';
+import { MatTooltip } from '@angular/material/tooltip';
+import { ProjectService } from '../../services/projectService';
+import { MatIcon } from '@angular/material/icon';
+import { StudentProgress } from '../student-progress/StudentProgress';
+
+@Component({
+ imports: [MatIcon, MatTooltip],
+ selector: 'project-location',
+ styleUrl: './project-location.component.scss',
+ templateUrl: './project-location.component.html'
+})
+export class ProjectLocationComponent {
+ private projectService: ProjectService = inject(ProjectService);
+
+ protected currentSegment: any;
+ protected segments: any[];
+ @Input() studentProgress: StudentProgress;
+
+ ngOnChanges(): void {
+ const groupNodes = this.projectService.getOrderedGroupNodes();
+ if (groupNodes.length > 1) {
+ this.segments = groupNodes;
+ this.currentSegment = this.projectService.getParentGroup(this.studentProgress.currentNodeId);
+ } else {
+ this.segments = this.getOrderedNodes();
+ this.currentSegment = this.projectService.getNode(this.studentProgress.currentNodeId);
+ }
+ }
+
+ private getOrderedNodes(): any[] {
+ const idToOrder = this.projectService.idToOrder;
+ return this.projectService
+ .getNodes()
+ .filter((node) => node.type !== 'group')
+ .sort((a, b) => idToOrder[a.id].order - idToOrder[b.id].order);
+ }
+}
diff --git a/src/assets/wise5/classroomMonitor/student-progress/StudentProgress.ts b/src/assets/wise5/classroomMonitor/student-progress/StudentProgress.ts
new file mode 100644
index 00000000000..4dd3dd5dbcf
--- /dev/null
+++ b/src/assets/wise5/classroomMonitor/student-progress/StudentProgress.ts
@@ -0,0 +1,25 @@
+import { ProjectCompletion } from '../../common/ProjectCompletion';
+
+export class StudentProgress {
+ currentNodeId: string;
+ periodId: string;
+ periodName: string;
+ workgroupId: number;
+ username: string;
+ firstName: string;
+ lastName: string;
+ nodePosition: string;
+ positionAndTitle: string;
+ order: number;
+ completion: ProjectCompletion;
+ completionPct: number;
+ score: number;
+ maxScore: number;
+ scorePct: number;
+
+ constructor(jsonObject: any = {}) {
+ for (const key of Object.keys(jsonObject)) {
+ this[key] = jsonObject[key];
+ }
+ }
+}
diff --git a/src/assets/wise5/classroomMonitor/student-progress/student-progress.component.html b/src/assets/wise5/classroomMonitor/student-progress/student-progress.component.html
index 07d770d9eed..8cfb85cc302 100644
--- a/src/assets/wise5/classroomMonitor/student-progress/student-progress.component.html
+++ b/src/assets/wise5/classroomMonitor/student-progress/student-progress.component.html
@@ -54,7 +54,9 @@
} @else {
{{ student.username }} |
}
- {{ student.position }} |
+
+
+ |
{
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [ClassroomMonitorTestingModule, StudentProgressComponent],
+ imports: [
+ ClassroomMonitorTestingModule,
+ MockComponent(ProjectLocationComponent),
+ StudentProgressComponent
+ ],
providers: [provideRouter([])]
}).compileComponents();
});
diff --git a/src/assets/wise5/classroomMonitor/student-progress/student-progress.component.ts b/src/assets/wise5/classroomMonitor/student-progress/student-progress.component.ts
index 73021440642..be589940da1 100644
--- a/src/assets/wise5/classroomMonitor/student-progress/student-progress.component.ts
+++ b/src/assets/wise5/classroomMonitor/student-progress/student-progress.component.ts
@@ -4,7 +4,6 @@ import { ConfigService } from '../../services/configService';
import { ClassroomStatusService } from '../../services/classroomStatusService';
import { TeacherDataService } from '../../services/teacherDataService';
import { ActivatedRoute, Router } from '@angular/router';
-import { ProjectCompletion } from '../../common/ProjectCompletion';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
@@ -12,6 +11,8 @@ import { MatTableModule } from '@angular/material/table';
import { CommonModule } from '@angular/common';
import { WorkgroupSelectAutocompleteComponent } from '../../../../app/classroom-monitor/workgroup-select/workgroup-select-autocomplete/workgroup-select-autocomplete.component';
import { ProjectProgressComponent } from '../classroomMonitorComponents/studentProgress/project-progress/project-progress.component';
+import { ProjectLocationComponent } from '../project-location/project-location.component';
+import { StudentProgress } from './StudentProgress';
@Component({
encapsulation: ViewEncapsulation.None,
@@ -21,6 +22,7 @@ import { ProjectProgressComponent } from '../classroomMonitorComponents/studentP
MatIconModule,
MatListModule,
MatTableModule,
+ ProjectLocationComponent,
ProjectProgressComponent,
WorkgroupSelectAutocompleteComponent
],
@@ -129,7 +131,9 @@ export class StudentProgressComponent implements OnInit {
this.students
.filter((student) => student.workgroupId === workgroupId)
.forEach((student) => {
- student.position = location?.position || '';
+ student.currentNodeId = location?.nodeId || '';
+ student.nodePosition = location?.nodePosition || '';
+ student.positionAndTitle = location?.positionAndTitle || '';
student.order = location?.order || 0;
student.completion = completion;
student.completionPct = completion.completionPct || 0;
@@ -200,25 +204,3 @@ export class StudentProgressComponent implements OnInit {
this.sortWorkgroups();
}
}
-
-export class StudentProgress {
- periodId: string;
- periodName: string;
- workgroupId: number;
- username: string;
- firstName: string;
- lastName: string;
- position: string;
- order: number;
- completion: ProjectCompletion;
- completionPct: number;
- score: number;
- maxScore: number;
- scorePct: number;
-
- constructor(jsonObject: any = {}) {
- for (const key of Object.keys(jsonObject)) {
- this[key] = jsonObject[key];
- }
- }
-}
diff --git a/src/assets/wise5/services/classroomStatusService.ts b/src/assets/wise5/services/classroomStatusService.ts
index 7e2f7c0b4fc..a52909c159e 100644
--- a/src/assets/wise5/services/classroomStatusService.ts
+++ b/src/assets/wise5/services/classroomStatusService.ts
@@ -56,7 +56,9 @@ export class ClassroomStatusService {
if (studentStatus != null) {
const currentNodeId = studentStatus.currentNodeId;
return {
- position: this.projectService.getNodePositionAndTitle(currentNodeId),
+ nodeId: currentNodeId,
+ nodePosition: this.projectService.getNodePositionById(currentNodeId),
+ positionAndTitle: this.projectService.getNodePositionAndTitle(currentNodeId),
order: this.projectService.getNodeOrderById(currentNodeId)
};
}
diff --git a/src/messages.xlf b/src/messages.xlf
index 57fb072b317..ab58ef47772 100644
--- a/src/messages.xlf
+++ b/src/messages.xlf
@@ -2701,7 +2701,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.
src/assets/wise5/classroomMonitor/student-progress/student-progress.component.ts
- 68
+ 70
src/assets/wise5/components/conceptMap/concept-map-student/concept-map-student.component.ts
@@ -3201,7 +3201,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.
src/assets/wise5/classroomMonitor/student-progress/student-progress.component.ts
- 43
+ 45
@@ -3444,7 +3444,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.
src/assets/wise5/classroomMonitor/student-progress/student-progress.component.ts
- 48
+ 50
@@ -3498,7 +3498,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.
src/assets/wise5/classroomMonitor/student-progress/student-progress.component.ts
- 53
+ 55
@@ -14359,7 +14359,7 @@ The branches will be removed but the steps will remain in the unit.
src/assets/wise5/classroomMonitor/student-progress/student-progress.component.ts
- 38
+ 40
@@ -15851,7 +15851,7 @@ Are you sure you want to proceed?
src/assets/wise5/classroomMonitor/student-progress/student-progress.component.ts
- 63
+ 65
@@ -15897,7 +15897,7 @@ Are you sure you want to proceed?
Location
src/assets/wise5/classroomMonitor/student-progress/student-progress.component.ts
- 58
+ 60
|