diff --git a/frontend/src/app/components/echo-header/echo-header.component.scss b/frontend/src/app/components/echo-header/echo-header.component.scss index c5b10ae..08f56a7 100644 --- a/frontend/src/app/components/echo-header/echo-header.component.scss +++ b/frontend/src/app/components/echo-header/echo-header.component.scss @@ -4,6 +4,9 @@ border-bottom: 1px solid var(--ee-color-border-subtle, #dccdb7); box-shadow: var(--ee-shadow-raised, 0 12px 28px rgba(16, 32, 51, 0.12)); backdrop-filter: none; + position: sticky; + top: 0; + z-index: 55; } .echo-header button, diff --git a/frontend/src/app/pages/home/home.component.scss b/frontend/src/app/pages/home/home.component.scss index 5936722..82c0434 100644 --- a/frontend/src/app/pages/home/home.component.scss +++ b/frontend/src/app/pages/home/home.component.scss @@ -43,10 +43,13 @@ .home-shell__main { min-width: 0; + max-height: 100vh; + overflow: hidden; } .home-shell__content { min-width: 0; + scroll-padding-top: 1rem; } .ee-shell__workspace { diff --git a/frontend/src/app/pages/learner-portal/learner-portal.component.spec.ts b/frontend/src/app/pages/learner-portal/learner-portal.component.spec.ts index 6490025..0943ae1 100644 --- a/frontend/src/app/pages/learner-portal/learner-portal.component.spec.ts +++ b/frontend/src/app/pages/learner-portal/learner-portal.component.spec.ts @@ -140,6 +140,19 @@ describe('LearnerPortalComponent', () => { expect(component.activeCourseStatusLabel).toBe('In progress'); }); + it('fills progress bars to the normalized percentage width', () => { + fixture.detectChanges(); + + const activeProgressBar = fixture.nativeElement.querySelector('[aria-label="Active course progress"]') as HTMLElement; + expect(activeProgressBar.style.width).toBe('42%'); + expect(activeProgressBar.getAttribute('aria-valuenow')).toBe('42'); + }); + + it('normalizes fractional progress values before rendering width', () => { + expect(component.normalizedProgress(0.42)).toBe(42); + expect(component.progressWidth(0.42)).toBe('42%'); + }); + it('navigates course continuation through the canonical /learn lesson route', () => { component.startCourse(makeStudentCourse()); diff --git a/frontend/src/app/pages/learner-portal/learner-portal.component.ts b/frontend/src/app/pages/learner-portal/learner-portal.component.ts index 160b31f..957a268 100644 --- a/frontend/src/app/pages/learner-portal/learner-portal.component.ts +++ b/frontend/src/app/pages/learner-portal/learner-portal.component.ts @@ -82,7 +82,7 @@ import { V2PlatformService } from '../../services/v2-platform.service';
0 && numericValue <= 1 ? numericValue * 100 : numericValue; + return Math.max(0, Math.min(100, percentValue)); + } + + progressWidth(value: number | undefined): string { + return `${this.normalizedProgress(value)}%`; } statusLabel(status: string | undefined, progress: number | undefined): string { diff --git a/frontend/src/app/pages/learner-portal/student-course-overview.component.spec.ts b/frontend/src/app/pages/learner-portal/student-course-overview.component.spec.ts index 85c1c82..4230ead 100644 --- a/frontend/src/app/pages/learner-portal/student-course-overview.component.spec.ts +++ b/frontend/src/app/pages/learner-portal/student-course-overview.component.spec.ts @@ -110,6 +110,7 @@ describe('StudentCourseOverviewComponent', () => { it('loads the authorized course overview with unit hierarchy and current lesson state', () => { const compiled = fixture.nativeElement as HTMLElement; + const progressBar = compiled.querySelector('[aria-label="Course progress"]') as HTMLElement; expect(coursesService.getCourseById).toHaveBeenCalledWith('course-1'); expect(compiled.textContent).toContain('Introduction to Africa'); @@ -119,6 +120,19 @@ describe('StudentCourseOverviewComponent', () => { expect(compiled.textContent).toContain('Locked'); expect(component.curriculumUnits[0].lessons[0].canOpen).toBeTrue(); expect(component.curriculumUnits[0].lessons[1].canOpen).toBeFalse(); + expect(progressBar.style.width).toBe('25%'); + expect(progressBar.getAttribute('aria-valuenow')).toBe('25'); + }); + + it('normalizes fractional course progress values before rendering width', () => { + coursesService.progressResponse = of(0.5); + fixture = TestBed.createComponent(StudentCourseOverviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + const progressBar = fixture.nativeElement.querySelector('[aria-label="Course progress"]') as HTMLElement; + expect(component.courseProgress).toBe(50); + expect(progressBar.style.width).toBe('50%'); }); it('resumes an in-progress course through the governed start-course endpoint', () => { diff --git a/frontend/src/app/pages/learner-portal/student-course-overview.component.ts b/frontend/src/app/pages/learner-portal/student-course-overview.component.ts index 2acd857..c0bcb81 100644 --- a/frontend/src/app/pages/learner-portal/student-course-overview.component.ts +++ b/frontend/src/app/pages/learner-portal/student-course-overview.component.ts @@ -86,7 +86,7 @@ import { ToastService } from '../../services/toast.service'; aria-valuemin="0" aria-valuemax="100" [attr.aria-valuenow]="courseProgress" - [style.width.%]="courseProgress" + [style.width]="progressWidth(courseProgress)" >
@@ -598,6 +598,12 @@ export class StudentCourseOverviewComponent implements OnInit { } private normalizeProgress(progress: number | undefined): number { - return Math.max(0, Math.min(100, progress || 0)); + const numericProgress = progress || 0; + const percentProgress = numericProgress > 0 && numericProgress <= 1 ? numericProgress * 100 : numericProgress; + return Math.max(0, Math.min(100, percentProgress)); + } + + progressWidth(progress: number | undefined): string { + return `${this.normalizeProgress(progress)}%`; } }