From 6283c326133ebe2bad827d80c287e16614c9628b Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Thu, 6 Mar 2025 23:30:50 +0100 Subject: [PATCH 01/49] migrate exam-bar.component --- .../exam-bar/exam-bar.component.html | 14 ++++---- .../exam-bar/exam-bar.component.ts | 36 +++++++++---------- .../participate/exam-bar.component.spec.ts | 17 +++++---- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/main/webapp/app/exam/participate/exam-bar/exam-bar.component.html b/src/main/webapp/app/exam/participate/exam-bar/exam-bar.component.html index 31bb0e9a0358..be508a4e1cc4 100644 --- a/src/main/webapp/app/exam/participate/exam-bar/exam-bar.component.html +++ b/src/main/webapp/app/exam/participate/exam-bar/exam-bar.component.html @@ -1,19 +1,19 @@ -
+

{{ examTitle }}

- @if (!examTimeLineView) { + @if (!examTimeLineView()) {
- - @if (!isEndView) { + + @if (!isEndView()) {
- @for (exercise of exercises; track exercise; let i = $index) { + @for (exercise of exercises(); track exercise; let i = $index) { @if ( i >= - exerciseIndex - + exerciseIndex() - itemsVisiblePerSide - - (exerciseIndex + 1 + itemsVisiblePerSide > exercises.length ? exerciseIndex + 1 + itemsVisiblePerSide - exercises.length : 0) && - i <= exerciseIndex + itemsVisiblePerSide - (exerciseIndex - itemsVisiblePerSide <= 0 ? exerciseIndex - itemsVisiblePerSide : 0) + (exerciseIndex() + 1 + itemsVisiblePerSide > exercises().length ? exerciseIndex() + 1 + itemsVisiblePerSide - exercises().length : 0) && + i <= exerciseIndex() + itemsVisiblePerSide - (exerciseIndex() - itemsVisiblePerSide <= 0 ? exerciseIndex() - itemsVisiblePerSide : 0) ) {
- @if (!examTimeLineView) { + @if (!examTimeLineView()) {
- @if (!overviewPageOpen) { + @if (!overviewPageOpen()) { }
- +
} diff --git a/src/main/webapp/app/exam/participate/exam-navigation-bar/exam-navigation-bar.component.ts b/src/main/webapp/app/exam/participate/exam-navigation-bar/exam-navigation-bar.component.ts index 28aab3a490b3..f99f23329423 100644 --- a/src/main/webapp/app/exam/participate/exam-navigation-bar/exam-navigation-bar.component.ts +++ b/src/main/webapp/app/exam/participate/exam-navigation-bar/exam-navigation-bar.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output, inject } from '@angular/core'; +import { AfterViewInit, Component, OnInit, inject, input, model, output } from '@angular/core'; import { Exercise, ExerciseType } from 'app/entities/exercise.model'; import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model'; import { LayoutService } from 'app/shared/breakpoints/layout.service'; @@ -38,20 +38,20 @@ export class ExamNavigationBarComponent implements OnInit, AfterViewInit { private repositoryService = inject(CodeEditorRepositoryService); private conflictService = inject(CodeEditorConflictStateService); - @Input() exercises: Exercise[] = []; - @Input() exerciseIndex = 0; - @Input() endDate: dayjs.Dayjs; - @Input() overviewPageOpen: boolean; - @Input() examSessions?: ExamSession[] = []; - @Input() examTimeLineView = false; - @Output() onPageChanged = new EventEmitter<{ + exercises = input([]); + exerciseIndex = model(0); + endDate = input(); + overviewPageOpen = input(); + examSessions = input([]); + examTimeLineView = input(false); + onPageChanged = output<{ overViewChange: boolean; exercise?: Exercise; forceSave: boolean; submission?: ProgrammingSubmission | SubmissionVersion | FileUploadSubmission; }>(); - @Output() examAboutToEnd = new EventEmitter(); - @Output() onExamHandInEarly = new EventEmitter(); + examAboutToEnd = output(); + onExamHandInEarly = output(); static itemsVisiblePerSideDefault = 4; itemsVisiblePerSide = ExamNavigationBarComponent.itemsVisiblePerSideDefault; @@ -66,7 +66,7 @@ export class ExamNavigationBarComponent implements OnInit, AfterViewInit { faBars = faBars; ngOnInit(): void { - if (!this.examTimeLineView) { + if (!this.examTimeLineView()) { this.subscriptionToLiveExamExerciseUpdates = this.examExerciseUpdateService.currentExerciseIdForNavigation.subscribe((exerciseIdToNavigateTo) => { // another exercise will only be displayed if the student clicks on the corresponding pop-up notification this.changeExerciseById(exerciseIdToNavigateTo); @@ -86,13 +86,13 @@ export class ExamNavigationBarComponent implements OnInit, AfterViewInit { } }); - const isInitialSession = this.examSessions && this.examSessions.length > 0 && this.examSessions[0].initialSession; + const isInitialSession = this.examSessions() && this.examSessions().length > 0 && this.examSessions()[0].initialSession; if (isInitialSession || isInitialSession == undefined) { return; } // If it is not an initial session, update the isSynced variable for out of sync submissions. - this.exercises + this.exercises() .filter((exercise) => exercise.type === ExerciseType.PROGRAMMING && exercise.studentParticipations) .forEach((exercise) => { const domain: DomainChange = [DomainType.PARTICIPATION, exercise.studentParticipations![0]]; @@ -130,27 +130,27 @@ export class ExamNavigationBarComponent implements OnInit, AfterViewInit { } /** - * @param overviewPage: user wants to switch to the overview page - * @param exerciseIndex: index of the exercise to switch to, if it should not be used, you can pass -1 - * @param forceSave: true if forceSave shall be used. + * @param overviewPage user wants to switch to the overview page + * @param exerciseIndex index of the exercise to switch to, if it should not be used, you can pass -1 + * @param forceSave true if forceSave shall be used. * @param submission the submission to be viewed, used in the exam timeline */ changePage(overviewPage: boolean, exerciseIndex: number, forceSave?: boolean, submission?: SubmissionVersion | ProgrammingSubmission | FileUploadSubmission): void { if (!overviewPage) { // out of index -> do nothing - if (exerciseIndex > this.exercises.length - 1 || exerciseIndex < 0) { + if (exerciseIndex > this.exercises().length - 1 || exerciseIndex < 0) { return; } // set index and emit event - this.exerciseIndex = exerciseIndex; - this.onPageChanged.emit({ overViewChange: false, exercise: this.exercises[this.exerciseIndex], forceSave: !!forceSave, submission: submission }); + this.exerciseIndex.update(() => exerciseIndex); + this.onPageChanged.emit({ overViewChange: false, exercise: this.exercises()[this.exerciseIndex()], forceSave: !!forceSave, submission: submission }); } else if (overviewPage) { // set index and emit event - this.exerciseIndex = -1; + this.exerciseIndex.update(() => -1); // save current exercise this.onPageChanged.emit({ overViewChange: true, exercise: undefined, forceSave: false }); } - this.setExerciseButtonStatus(this.exerciseIndex); + this.setExerciseButtonStatus(this.exerciseIndex()); } /** @@ -158,7 +158,7 @@ export class ExamNavigationBarComponent implements OnInit, AfterViewInit { * @param exerciseId the unique identifier of an exercise that stays the same regardless of student exam ordering */ changeExerciseById(exerciseId: number) { - const foundIndex = this.exercises.findIndex((exercise) => exercise.id === exerciseId); + const foundIndex = this.exercises().findIndex((exercise) => exercise.id === exerciseId); this.changePage(false, foundIndex, true); } @@ -167,16 +167,16 @@ export class ExamNavigationBarComponent implements OnInit, AfterViewInit { * @param changeExercise whether to go to the next exercise {boolean} */ saveExercise(changeExercise = true) { - const newIndex = this.exerciseIndex + 1; - const submission = ExamParticipationService.getSubmissionForExercise(this.exercises[this.exerciseIndex]); + const newIndex = this.exerciseIndex() + 1; + const submission = ExamParticipationService.getSubmissionForExercise(this.exercises()[this.exerciseIndex()]); // we do not submit programming exercises on a save - if (submission && this.exercises[this.exerciseIndex].type !== ExerciseType.PROGRAMMING) { + if (submission && this.exercises()[this.exerciseIndex()].type !== ExerciseType.PROGRAMMING) { submission.submitted = true; } if (changeExercise) { - if (newIndex > this.exercises.length - 1) { + if (newIndex > this.exercises().length - 1) { // we are in the last exercise, if out of range "change" active exercise to current in order to trigger a save - this.changePage(false, this.exerciseIndex, true); + this.changePage(false, this.exerciseIndex(), true); } else { this.changePage(false, newIndex, true); } @@ -184,15 +184,15 @@ export class ExamNavigationBarComponent implements OnInit, AfterViewInit { } isProgrammingExercise() { - return this.exercises[this.exerciseIndex].type === ExerciseType.PROGRAMMING; + return this.exercises()[this.exerciseIndex()].type === ExerciseType.PROGRAMMING; } isFileUploadExercise() { - return this.exercises[this.exerciseIndex].type === ExerciseType.FILE_UPLOAD; + return this.exercises()[this.exerciseIndex()].type === ExerciseType.FILE_UPLOAD; } getOverviewStatus(): 'active' | '' { - return this.overviewPageOpen ? 'active' : ''; + return this.overviewPageOpen() ? 'active' : ''; } /** @@ -208,15 +208,15 @@ export class ExamNavigationBarComponent implements OnInit, AfterViewInit { this.icon = faCheck; // If we are in the exam timeline we do not use not synced as not synced shows // that the current submission is not saved which doesn't make sense in the timeline. - if (this.examTimeLineView) { - return this.exerciseIndex === exerciseIndex ? 'synced active' : 'synced'; + if (this.examTimeLineView()) { + return this.exerciseIndex() === exerciseIndex ? 'synced active' : 'synced'; } // start with a yellow status (edit icon) // TODO: it's a bit weird, that it works that multiple icons (one per exercise) are hold in the same instance variable of the component // we should definitely refactor this and e.g. use the same ExamExerciseOverviewItem as in exam-exercise-overview-page.component.ts ! this.icon = faEdit; - const exercise = this.exercises[exerciseIndex]; + const exercise = this.exercises()[exerciseIndex]; const submission = ExamParticipationService.getSubmissionForExercise(exercise); if (!submission) { // in case no participation/submission yet exists -> display synced @@ -228,7 +228,7 @@ export class ExamNavigationBarComponent implements OnInit, AfterViewInit { } if (submission.isSynced || this.isOnlyOfflineIDE(exercise)) { // make button blue (except for the current page) - if (exerciseIndex === this.exerciseIndex && !this.overviewPageOpen) { + if (exerciseIndex === this.exerciseIndex() && !this.overviewPageOpen()!) { return 'synced active'; } else { return 'synced'; diff --git a/src/test/javascript/spec/component/exam/participate/exam-navigation-bar.component.spec.ts b/src/test/javascript/spec/component/exam/participate/exam-navigation-bar.component.spec.ts index f095c7bbc7d9..ea0adff40127 100644 --- a/src/test/javascript/spec/component/exam/participate/exam-navigation-bar.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/exam-navigation-bar.component.spec.ts @@ -17,6 +17,7 @@ import { MockTranslateService } from '../../../helpers/mocks/service/mock-transl import { TranslateService } from '@ngx-translate/core'; import { provideHttpClient } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { input, model } from '@angular/core'; describe('Exam Navigation Bar Component', () => { let fixture: ComponentFixture; @@ -45,8 +46,7 @@ describe('Exam Navigation Bar Component', () => { repositoryService = fixture.debugElement.injector.get(CodeEditorRepositoryService); TestBed.inject(ExamParticipationService); - comp.endDate = dayjs(); - comp.exercises = [ + const exercises = [ { id: 0, type: ExerciseType.PROGRAMMING, @@ -59,6 +59,10 @@ describe('Exam Navigation Bar Component', () => { { id: 1, type: ExerciseType.TEXT } as Exercise, { id: 2, type: ExerciseType.MODELING } as Exercise, ]; + TestBed.runInInjectionContext(() => { + comp.endDate = input(dayjs()); + comp.exercises = input(exercises); + }); }); beforeEach(fakeAsync(() => { @@ -69,8 +73,11 @@ describe('Exam Navigation Bar Component', () => { it('should update the submissions onInit if their CommitState is UNCOMMITTED_CHANGES to isSynced false, if not in initial session', () => { // Given // Create an exam session, which is not an initial session. - comp.examSessions = [{ initialSession: false } as ExamSession]; - const exerciseToBeSynced = comp.exercises[0]; + const examSessions = [{ initialSession: false } as ExamSession]; + const exerciseToBeSynced = comp.exercises()[0]; + TestBed.runInInjectionContext(() => { + comp.examSessions = input(examSessions); + }); jest.spyOn(repositoryService, 'getStatus').mockReturnValue(of({ repositoryStatus: CommitState.UNCOMMITTED_CHANGES })); // When @@ -95,14 +102,14 @@ describe('Exam Navigation Bar Component', () => { jest.spyOn(comp.onPageChanged, 'emit'); jest.spyOn(comp, 'setExerciseButtonStatus'); - expect(comp.exerciseIndex).toBe(0); + expect(comp.exerciseIndex()).toBe(0); const exerciseIndex = 1; const force = false; comp.changePage(false, exerciseIndex, force); - expect(comp.exerciseIndex).toEqual(exerciseIndex); + expect(comp.exerciseIndex()).toEqual(exerciseIndex); expect(comp.onPageChanged.emit).toHaveBeenCalledOnce(); expect(comp.setExerciseButtonStatus).toHaveBeenCalledWith(exerciseIndex); }); @@ -111,32 +118,38 @@ describe('Exam Navigation Bar Component', () => { jest.spyOn(comp.onPageChanged, 'emit'); jest.spyOn(comp, 'setExerciseButtonStatus'); - expect(comp.exerciseIndex).toBe(0); + expect(comp.exerciseIndex()).toBe(0); const exerciseIndex = 5; const force = false; comp.changePage(false, exerciseIndex, force); - expect(comp.exerciseIndex).toBe(0); + expect(comp.exerciseIndex()).toBe(0); expect(comp.onPageChanged.emit).not.toHaveBeenCalled(); expect(comp.setExerciseButtonStatus).not.toHaveBeenCalledWith(exerciseIndex); }); it('should tell the type of the selected programming exercise', () => { - comp.exerciseIndex = 0; + TestBed.runInInjectionContext(() => { + comp.exerciseIndex = model(0); + }); expect(comp.isProgrammingExercise()).toBeTrue(); }); it('should tell the type of the selected text exercise', () => { - comp.exerciseIndex = 1; + TestBed.runInInjectionContext(() => { + comp.exerciseIndex = model(1); + }); expect(comp.isProgrammingExercise()).toBeFalse(); }); it('should tell the type of the selected modeling exercise', () => { - comp.exerciseIndex = 2; + TestBed.runInInjectionContext(() => { + comp.exerciseIndex = model(2); + }); expect(comp.isProgrammingExercise()).toBeFalse(); }); @@ -174,7 +187,7 @@ describe('Exam Navigation Bar Component', () => { }); it('should set the exercise button status for submitted submission', () => { - comp.exercises[0].studentParticipations![0].submissions![0] = { submitted: true }; + comp.exercises()[0].studentParticipations![0].submissions![0] = { submitted: true }; const result = comp.setExerciseButtonStatus(0); @@ -183,7 +196,7 @@ describe('Exam Navigation Bar Component', () => { }); it('should set the exercise button status for submitted and synced submission active', () => { - comp.exercises[0].studentParticipations![0].submissions![0] = { submitted: true, isSynced: true }; + comp.exercises()[0].studentParticipations![0].submissions![0] = { submitted: true, isSynced: true }; const result = comp.setExerciseButtonStatus(0); @@ -191,7 +204,7 @@ describe('Exam Navigation Bar Component', () => { }); it('should set the exercise button status for submitted and synced submission not active', () => { - comp.exercises[0].studentParticipations![0].submissions![0] = { submitted: true, isSynced: true }; + comp.exercises()[0].studentParticipations![0].submissions![0] = { submitted: true, isSynced: true }; const result = comp.setExerciseButtonStatus(1); @@ -199,39 +212,39 @@ describe('Exam Navigation Bar Component', () => { }); it('should get the exercise button tooltip without submission', () => { - const result = comp.getExerciseButtonTooltip(comp.exercises[1]); + const result = comp.getExerciseButtonTooltip(comp.exercises()[1]); expect(result).toBe('synced'); }); it('should get the exercise button tooltip with submitted and synced submission', () => { - comp.exercises[0].studentParticipations![0].submissions![0] = { submitted: true, isSynced: true }; + comp.exercises()[0].studentParticipations![0].submissions![0] = { submitted: true, isSynced: true }; - const result = comp.getExerciseButtonTooltip(comp.exercises[0]); + const result = comp.getExerciseButtonTooltip(comp.exercises()[0]); expect(result).toBe('submitted'); }); it('should get the exercise button tooltip with submitted submission', () => { - comp.exercises[0].studentParticipations![0].submissions![0] = { submitted: true }; + comp.exercises()[0].studentParticipations![0].submissions![0] = { submitted: true }; - const result = comp.getExerciseButtonTooltip(comp.exercises[0]); + const result = comp.getExerciseButtonTooltip(comp.exercises()[0]); expect(result).toBe('notSavedOrSubmitted'); }); it('should get the exercise button tooltip with submission', () => { - comp.exercises[0].studentParticipations![0].submissions![0] = {}; + comp.exercises()[0].studentParticipations![0].submissions![0] = {}; - const result = comp.getExerciseButtonTooltip(comp.exercises[0]); + const result = comp.getExerciseButtonTooltip(comp.exercises()[0]); expect(result).toBe('notSavedOrSubmitted'); }); it('should get the exercise button tooltip with synced submission', () => { - comp.exercises[0].studentParticipations![0].submissions![0] = { isSynced: true }; + comp.exercises()[0].studentParticipations![0].submissions![0] = { isSynced: true }; - const result = comp.getExerciseButtonTooltip(comp.exercises[0]); + const result = comp.getExerciseButtonTooltip(comp.exercises()[0]); expect(result).toBe('notSubmitted'); }); @@ -244,15 +257,19 @@ describe('Exam Navigation Bar Component', () => { }); it('should set exercise button status to synced active if it is the active exercise in the exam timeline view', () => { - comp.examTimeLineView = true; - comp.exerciseIndex = 0; + TestBed.runInInjectionContext(() => { + comp.examTimeLineView = input(true); + comp.exerciseIndex = model(0); + }); expect(comp.setExerciseButtonStatus(0)).toBe('synced active'); expect(comp.icon).toEqual(faCheck); }); it('should set exercise button status to synced if it is not the active exercise in the exam timeline view', () => { - comp.examTimeLineView = true; - comp.exerciseIndex = 0; + TestBed.runInInjectionContext(() => { + comp.examTimeLineView = input(true); + comp.exerciseIndex = model(0); + }); expect(comp.setExerciseButtonStatus(1)).toBe('synced'); expect(comp.icon).toEqual(faCheck); }); From 605f5c0025c61b1b323304f00e6d95b255e0b95d Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Sun, 9 Mar 2025 18:56:50 +0100 Subject: [PATCH 23/49] set default value --- .../exam-navigation-bar/exam-navigation-bar.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/app/exam/participate/exam-navigation-bar/exam-navigation-bar.component.ts b/src/main/webapp/app/exam/participate/exam-navigation-bar/exam-navigation-bar.component.ts index f99f23329423..98634b6bea93 100644 --- a/src/main/webapp/app/exam/participate/exam-navigation-bar/exam-navigation-bar.component.ts +++ b/src/main/webapp/app/exam/participate/exam-navigation-bar/exam-navigation-bar.component.ts @@ -40,7 +40,7 @@ export class ExamNavigationBarComponent implements OnInit, AfterViewInit { exercises = input([]); exerciseIndex = model(0); - endDate = input(); + endDate = input(dayjs()); overviewPageOpen = input(); examSessions = input([]); examTimeLineView = input(false); From 21f9921aabee48984be09a1124a9557fa5ff467e Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Sun, 9 Mar 2025 19:12:47 +0100 Subject: [PATCH 24/49] migrate exam-participation-cover.component --- .../exam-participation-cover.component.html | 32 ++++---- .../exam-participation-cover.component.ts | 76 +++++++++---------- ...exam-participation-cover.component.spec.ts | 69 +++++++++-------- 3 files changed, 93 insertions(+), 84 deletions(-) diff --git a/src/main/webapp/app/exam/participate/exam-cover/exam-participation-cover.component.html b/src/main/webapp/app/exam/participate/exam-cover/exam-participation-cover.component.html index 9e626d906094..ec27571eae96 100644 --- a/src/main/webapp/app/exam/participate/exam-cover/exam-participation-cover.component.html +++ b/src/main/webapp/app/exam/participate/exam-cover/exam-participation-cover.component.html @@ -3,20 +3,20 @@ -->
- @if (startView) { + @if (startView()) {

- {{ exam.title }} + {{ exam().title }}

- +

- +
@@ -26,7 +26,7 @@

id="confirmBox" (click)="updateConfirmation()" class="form-check-input me-2" - [class.ms-0]="!this.exam.confirmationStartText" + [class.ms-0]="!this.exam().confirmationStartText" [required]="inserted" [disabled]="waitingForExamStart" /> @@ -90,14 +90,14 @@

@if (waitingForExamStart) {
- - @if (exam.startDate) { + + @if (exam().startDate) {

{{ timeUntilStart }}
- ({{ exam.startDate | artemisDate: 'time' }}) + ({{ exam().startDate | artemisDate: 'time' }})
}
@@ -108,7 +108,7 @@

@@ -119,7 +119,7 @@

- @if (handInEarly) { + @if (handInEarly()) {
@@ -143,7 +143,7 @@

id="confirmBox" (click)="updateConfirmation()" class="form-check-input me-2" - [class.ms-0]="!this.exam.confirmationEndText" + [class.ms-0]="!this.exam().confirmationEndText" [required]="inserted" /> @@ -193,20 +193,20 @@

- @if (handInEarly) { + @if (handInEarly()) {
}
- @if (handInEarly) { - }
-
+
diff --git a/src/main/webapp/app/exam/participate/summary/collapsible-card.component.ts b/src/main/webapp/app/exam/participate/summary/collapsible-card.component.ts index 6d1eeceb1933..47b46cf28ba5 100644 --- a/src/main/webapp/app/exam/participate/summary/collapsible-card.component.ts +++ b/src/main/webapp/app/exam/participate/summary/collapsible-card.component.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, input } from '@angular/core'; import { faAngleRight } from '@fortawesome/free-solid-svg-icons'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { NgbCollapse } from '@ng-bootstrap/ng-bootstrap'; @@ -10,8 +10,8 @@ import { NgbCollapse } from '@ng-bootstrap/ng-bootstrap'; imports: [FaIconComponent, NgbCollapse], }) export class CollapsibleCardComponent { - @Input() isCardContentCollapsed: boolean; - @Input() toggleCollapse: () => void; + isCardContentCollapsed = input.required(); + toggleCollapse = input.required<() => void>(); faAngleRight = faAngleRight; } diff --git a/src/test/javascript/spec/component/exam/participate/summary/collapsible-card.component.spec.ts b/src/test/javascript/spec/component/exam/participate/summary/collapsible-card.component.spec.ts index b1db2cae3153..8c4dc8606f33 100644 --- a/src/test/javascript/spec/component/exam/participate/summary/collapsible-card.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/summary/collapsible-card.component.spec.ts @@ -1,6 +1,7 @@ import { ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing'; import { CollapsibleCardComponent } from 'app/exam/participate/summary/collapsible-card.component'; import { By } from '@angular/platform-browser'; +import { input } from '@angular/core'; let fixture: ComponentFixture; let component: CollapsibleCardComponent; @@ -13,13 +14,15 @@ describe('CollapsibleCardComponent', () => { fixture = TestBed.createComponent(CollapsibleCardComponent); component = fixture.componentInstance; - component.toggleCollapse = () => {}; - component.isCardContentCollapsed = false; + TestBed.runInInjectionContext(() => { + component.toggleCollapse = input(() => {}); + component.isCardContentCollapsed = input(false); + }); }); }); it('should collapse and expand exercise when collapse button is clicked', fakeAsync(() => { - const toggleCollapseSpy = jest.spyOn(component, 'toggleCollapse').mockImplementation(() => {}); + const toggleCollapseSpy = jest.spyOn(component, 'toggleCollapse'); fixture.detectChanges(); const toggleCollapseHeader = fixture.debugElement.query(By.css('.card-header')); From c288d30fe426b2374aa2bbc42a57a5841849ae07 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Sun, 9 Mar 2025 19:53:20 +0100 Subject: [PATCH 26/49] migrate overlay and button for live events --- .../exam-live-events-button.component.ts | 23 +++++++++++-------- .../exam-live-events-overlay.component.ts | 8 +++---- .../exam-result-summary.component.html | 2 +- .../summary/exam-result-summary.component.ts | 5 ++-- .../exam-result-overview.component.ts | 2 +- .../exam-live-events-button.component.spec.ts | 8 ++++--- ...exam-live-events-overlay.component.spec.ts | 7 ++++-- 7 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/main/webapp/app/exam/participate/events/exam-live-events-button.component.ts b/src/main/webapp/app/exam/participate/events/exam-live-events-button.component.ts index 91a0fcf88fa8..645264b389a1 100644 --- a/src/main/webapp/app/exam/participate/events/exam-live-events-button.component.ts +++ b/src/main/webapp/app/exam/participate/events/exam-live-events-button.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnDestroy, OnInit, ViewEncapsulation, inject } from '@angular/core'; +import { Component, OnDestroy, OnInit, ViewEncapsulation, inject, input } from '@angular/core'; import { faBullhorn } from '@fortawesome/free-solid-svg-icons'; import { AlertService } from 'app/core/util/alert.service'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; @@ -32,7 +32,7 @@ export class ExamLiveEventsButtonComponent implements OnInit, OnDestroy { private liveEventsSubscription?: Subscription; private allEventsSubscription?: Subscription; eventCount = 0; - @Input() examStartDate: dayjs.Dayjs; + examStartDate = input(); // Icons faBullhorn = faBullhorn; @@ -40,16 +40,19 @@ export class ExamLiveEventsButtonComponent implements OnInit, OnDestroy { ngOnInit(): void { this.allEventsSubscription = this.liveEventsService.observeAllEvents(USER_DISPLAY_RELEVANT_EVENTS_REOPEN).subscribe((events: ExamLiveEvent[]) => { // do not count the problem statements events that are made before the start of the exam - const filteredEvents = events.filter((event) => !(event.eventType === ExamLiveEventType.PROBLEM_STATEMENT_UPDATE && event.createdDate.isBefore(this.examStartDate))); + const filteredEvents = events.filter((event) => !(event.eventType === ExamLiveEventType.PROBLEM_STATEMENT_UPDATE && event.createdDate.isBefore(this.examStartDate()))); this.eventCount = filteredEvents.length; }); - this.liveEventsSubscription = this.liveEventsService.observeNewEventsAsUser(USER_DISPLAY_RELEVANT_EVENTS, this.examStartDate).subscribe(() => { - // If any unacknowledged event comes in, open the dialog to display it - if (!this.modalRef) { - this.openDialog(); - } - }); + const examStartDate = this.examStartDate(); + if (examStartDate) { + this.liveEventsSubscription = this.liveEventsService.observeNewEventsAsUser(USER_DISPLAY_RELEVANT_EVENTS, examStartDate).subscribe(() => { + // If any unacknowledged event comes in, open the dialog to display it + if (!this.modalRef) { + this.openDialog(); + } + }); + } } ngOnDestroy(): void { @@ -69,7 +72,7 @@ export class ExamLiveEventsButtonComponent implements OnInit, OnDestroy { windowClass: 'live-events-modal-window', }); - this.modalRef.componentInstance.examStartDate = this.examStartDate; + this.modalRef.componentInstance.examStartDate.update(() => this.examStartDate()); from(this.modalRef.result).subscribe(() => (this.modalRef = undefined)); } diff --git a/src/main/webapp/app/exam/participate/events/exam-live-events-overlay.component.ts b/src/main/webapp/app/exam/participate/events/exam-live-events-overlay.component.ts index b1c1e7f99c1c..0ce6303a0e01 100644 --- a/src/main/webapp/app/exam/participate/events/exam-live-events-overlay.component.ts +++ b/src/main/webapp/app/exam/participate/events/exam-live-events-overlay.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnDestroy, OnInit, inject } from '@angular/core'; +import { Component, OnDestroy, OnInit, inject, model } from '@angular/core'; import { faCheck } from '@fortawesome/free-solid-svg-icons'; import { ExamLiveEventComponent } from 'app/exam/shared/events/exam-live-event.component'; import { Subscription } from 'rxjs'; @@ -28,7 +28,7 @@ export class ExamLiveEventsOverlayComponent implements OnInit, OnDestroy { eventsToDisplay?: ExamLiveEvent[]; events: ExamLiveEvent[] = []; - @Input() examStartDate: dayjs.Dayjs; + examStartDate = model.required(); // Icons faCheck = faCheck; @@ -42,13 +42,13 @@ export class ExamLiveEventsOverlayComponent implements OnInit, OnDestroy { ngOnInit(): void { this.allLiveEventsSubscription = this.liveEventsService.observeAllEvents(USER_DISPLAY_RELEVANT_EVENTS_REOPEN).subscribe((events: ExamLiveEvent[]) => { // display the problem statements events only after the start of the exam - this.events = events.filter((event) => !(event.eventType === ExamLiveEventType.PROBLEM_STATEMENT_UPDATE && event.createdDate.isBefore(this.examStartDate))); + this.events = events.filter((event) => !(event.eventType === ExamLiveEventType.PROBLEM_STATEMENT_UPDATE && event.createdDate.isBefore(this.examStartDate()))); if (!this.eventsToDisplay) { this.updateEventsToDisplay(); } }); - this.newLiveEventsSubscription = this.liveEventsService.observeNewEventsAsUser(USER_DISPLAY_RELEVANT_EVENTS, this.examStartDate).subscribe((event: ExamLiveEvent) => { + this.newLiveEventsSubscription = this.liveEventsService.observeNewEventsAsUser(USER_DISPLAY_RELEVANT_EVENTS, this.examStartDate()).subscribe((event: ExamLiveEvent) => { this.unacknowledgedEvents.unshift(event); this.updateEventsToDisplay(); }); diff --git a/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.html b/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.html index dbf401451af5..005b681f49d8 100644 --- a/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.html +++ b/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.html @@ -190,7 +190,7 @@

[resultsPublished]="resultsArePublished" [isPrinting]="isPrinting" [isAfterResultsArePublished]="resultsArePublished" - [instructorView]="instructorView" + [instructorView]="instructorView()" /> } } diff --git a/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.ts b/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.ts index f50d7b278a6e..1e5f95672000 100644 --- a/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.ts +++ b/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit, inject } from '@angular/core'; +import { Component, Input, OnInit, inject, input } from '@angular/core'; import { StudentExam } from 'app/entities/student-exam.model'; import { Exercise, ExerciseType, IncludedInOverallScore, getIcon } from 'app/entities/exercise.model'; import dayjs from 'dayjs/esm'; @@ -142,8 +142,7 @@ export class ExamResultSummaryComponent implements OnInit { isGradingKeyCollapsed = true; isBonusGradingKeyCollapsed = true; - @Input() - instructorView = false; + instructorView = input(false); courseId: number; diff --git a/src/main/webapp/app/exam/participate/summary/result-overview/exam-result-overview.component.ts b/src/main/webapp/app/exam/participate/summary/result-overview/exam-result-overview.component.ts index 5c107a0235a1..76be02984ee9 100644 --- a/src/main/webapp/app/exam/participate/summary/result-overview/exam-result-overview.component.ts +++ b/src/main/webapp/app/exam/participate/summary/result-overview/exam-result-overview.component.ts @@ -142,7 +142,7 @@ export class ExamResultOverviewComponent implements OnInit, OnChanges { let summedPercentages = 0; let numberOfExercises = 0; - Object.entries(this.exerciseInfos()).forEach(([, exerciseInfo]) => { + Object.entries(this.exerciseInfos).forEach(([, exerciseInfo]) => { summedPercentages += exerciseInfo.achievedPercentage ?? 0; numberOfExercises++; }); diff --git a/src/test/javascript/spec/component/exam/participate/events/exam-live-events-button.component.spec.ts b/src/test/javascript/spec/component/exam/participate/events/exam-live-events-button.component.spec.ts index ef2e15911637..c48e2a2253c4 100644 --- a/src/test/javascript/spec/component/exam/participate/events/exam-live-events-button.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/events/exam-live-events-button.component.spec.ts @@ -8,6 +8,8 @@ import { of } from 'rxjs'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { MockExamParticipationLiveEventsService } from '../../../../helpers/mocks/service/mock-exam-participation-live-events.service'; import { ExamLiveEventsOverlayComponent } from 'app/exam/participate/events/exam-live-events-overlay.component'; +import { input } from '@angular/core'; +import dayjs from 'dayjs/esm'; describe('ExamLiveEventsButtonComponent', () => { let component: ExamLiveEventsButtonComponent; @@ -21,13 +23,13 @@ describe('ExamLiveEventsButtonComponent', () => { imports: [MockModule(FontAwesomeModule)], providers: [MockProvider(AlertService), MockProvider(NgbModal), { provide: ExamParticipationLiveEventsService, useClass: MockExamParticipationLiveEventsService }], }).compileComponents(); - }); - - beforeEach(() => { fixture = TestBed.createComponent(ExamLiveEventsButtonComponent); component = fixture.componentInstance; mockModalService = TestBed.inject(NgbModal); mockLiveEventsService = TestBed.inject(ExamParticipationLiveEventsService); + TestBed.runInInjectionContext(() => { + component.examStartDate = input(dayjs()); + }); fixture.detectChanges(); }); diff --git a/src/test/javascript/spec/component/exam/participate/events/exam-live-events-overlay.component.spec.ts b/src/test/javascript/spec/component/exam/participate/events/exam-live-events-overlay.component.spec.ts index a6f967d4df05..5b000540ef7f 100644 --- a/src/test/javascript/spec/component/exam/participate/events/exam-live-events-overlay.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/events/exam-live-events-overlay.component.spec.ts @@ -10,6 +10,8 @@ import { MockSyncStorage } from '../../../../helpers/mocks/service/mock-sync-sto import { provideHttpClientTesting } from '@angular/common/http/testing'; import { provideHttpClient } from '@angular/common/http'; import { MockProvider } from 'ng-mocks'; +import dayjs from 'dayjs/esm'; +import { model } from '@angular/core'; describe('ExamLiveEventsOverlayComponent', () => { let component: ExamLiveEventsOverlayComponent; @@ -28,14 +30,15 @@ describe('ExamLiveEventsOverlayComponent', () => { MockProvider(NgbActiveModal), ], }).compileComponents(); - }); - beforeEach(() => { fixture = TestBed.createComponent(ExamLiveEventsOverlayComponent); component = fixture.componentInstance; mockLiveEventsService = TestBed.inject(ExamParticipationLiveEventsService); mockExamExerciseUpdateService = TestBed.inject(ExamExerciseUpdateService); mockActiveModal = TestBed.inject(NgbActiveModal); + TestBed.runInInjectionContext(() => { + component.examStartDate = model(dayjs()); + }); fixture.detectChanges(); }); From 6b5b5a3604b9db47427bcd03bcc0808104feffe5 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Sun, 9 Mar 2025 21:45:12 +0100 Subject: [PATCH 27/49] fix issue with model --- .../events/exam-live-events-overlay.component.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/webapp/app/exam/participate/events/exam-live-events-overlay.component.ts b/src/main/webapp/app/exam/participate/events/exam-live-events-overlay.component.ts index 0ce6303a0e01..d87bf954628b 100644 --- a/src/main/webapp/app/exam/participate/events/exam-live-events-overlay.component.ts +++ b/src/main/webapp/app/exam/participate/events/exam-live-events-overlay.component.ts @@ -28,7 +28,7 @@ export class ExamLiveEventsOverlayComponent implements OnInit, OnDestroy { eventsToDisplay?: ExamLiveEvent[]; events: ExamLiveEvent[] = []; - examStartDate = model.required(); + examStartDate = model(); // Icons faCheck = faCheck; @@ -48,10 +48,13 @@ export class ExamLiveEventsOverlayComponent implements OnInit, OnDestroy { } }); - this.newLiveEventsSubscription = this.liveEventsService.observeNewEventsAsUser(USER_DISPLAY_RELEVANT_EVENTS, this.examStartDate()).subscribe((event: ExamLiveEvent) => { - this.unacknowledgedEvents.unshift(event); - this.updateEventsToDisplay(); - }); + const examStartDate = this.examStartDate(); + if (examStartDate) { + this.newLiveEventsSubscription = this.liveEventsService.observeNewEventsAsUser(USER_DISPLAY_RELEVANT_EVENTS, examStartDate).subscribe((event: ExamLiveEvent) => { + this.unacknowledgedEvents.unshift(event); + this.updateEventsToDisplay(); + }); + } } acknowledgeEvent(event: ExamLiveEvent) { From d71cb6a782de2e1b9ebdc21d2c9ac742fc865ba7 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Mon, 17 Mar 2025 22:55:20 +0100 Subject: [PATCH 28/49] resolve conflict --- .../programming-exam-summary.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.ts b/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.ts index fdc2db61f984..ed895c8f1d75 100644 --- a/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.ts +++ b/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.ts @@ -104,13 +104,13 @@ export class ProgrammingExamSummaryComponent implements OnInit { get routerLinkForRepositoryView(): (string | number)[] { if (this.isInCourseManagement) { - return ['..', 'programming-exercises', this.exercise.id!, 'repository', 'USER', this.participation.id!]; + return ['..', 'programming-exercises', this.exercise().id!, 'repository', 'USER', this.participation().id!]; } if (this.routerLink.includes('test-exam')) { const parts = this.routerLink.split('/'); const examLink = parts.slice(0, parts.length - 2).join('/'); - return [examLink, 'exercises', this.exercise.id!, 'repository', this.participation.id!]; + return [examLink, 'exercises', this.exercise().id!, 'repository', this.participation().id!]; } - return [this.routerLink, 'exercises', this.exercise.id!, 'repository', this.participation.id!]; + return [this.routerLink, 'exercises', this.exercise().id!, 'repository', this.participation().id!]; } } From a4bdb44eb8bd61d50f292af8c3374bd4bbb24469 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Tue, 25 Mar 2025 12:52:22 +0100 Subject: [PATCH 29/49] resolve conflicts --- .../file-upload-exam-summary.component.ts | 2 -- .../events/exam-live-events-button.component.spec.ts | 1 - .../exam-result-summary-exercise-card-header.component.spec.ts | 2 -- 3 files changed, 5 deletions(-) diff --git a/src/main/webapp/app/exam/overview/summary/exercises/file-upload-exam-summary/file-upload-exam-summary.component.ts b/src/main/webapp/app/exam/overview/summary/exercises/file-upload-exam-summary/file-upload-exam-summary.component.ts index 351f35a52547..f9ae55ab3f26 100644 --- a/src/main/webapp/app/exam/overview/summary/exercises/file-upload-exam-summary/file-upload-exam-summary.component.ts +++ b/src/main/webapp/app/exam/overview/summary/exercises/file-upload-exam-summary/file-upload-exam-summary.component.ts @@ -1,7 +1,5 @@ -import { Component, Input } from '@angular/core'; import { FileUploadSubmission } from 'app/fileupload/shared/entities/file-upload-submission.model'; import { Component, input } from '@angular/core'; -import { FileUploadSubmission } from 'app/entities/file-upload-submission.model'; import { Exercise } from 'app/entities/exercise.model'; import { FileUploadSubmissionComponent } from 'app/fileupload/overview/file-upload-submission.component'; diff --git a/src/test/javascript/spec/component/exam/participate/events/exam-live-events-button.component.spec.ts b/src/test/javascript/spec/component/exam/participate/events/exam-live-events-button.component.spec.ts index 2c344ec12054..248bc15f8219 100644 --- a/src/test/javascript/spec/component/exam/participate/events/exam-live-events-button.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/events/exam-live-events-button.component.spec.ts @@ -7,7 +7,6 @@ import { ExamLiveEvent, ExamLiveEventType, ExamParticipationLiveEventsService } import { of } from 'rxjs'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { MockExamParticipationLiveEventsService } from '../../../../helpers/mocks/service/mock-exam-participation-live-events.service'; -import { ExamLiveEventsOverlayComponent } from 'app/exam/participate/events/exam-live-events-overlay.component'; import { input } from '@angular/core'; import dayjs from 'dayjs/esm'; import { ExamLiveEventsOverlayComponent } from 'app/exam/overview/events/exam-live-events-overlay.component'; diff --git a/src/test/javascript/spec/component/exam/participate/summary/exercises/header/exam-result-summary-exercise-card-header.component.spec.ts b/src/test/javascript/spec/component/exam/participate/summary/exercises/header/exam-result-summary-exercise-card-header.component.spec.ts index b39b9f8cba53..1a42434c7b0b 100644 --- a/src/test/javascript/spec/component/exam/participate/summary/exercises/header/exam-result-summary-exercise-card-header.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/summary/exercises/header/exam-result-summary-exercise-card-header.component.spec.ts @@ -12,8 +12,6 @@ import { SubmissionType } from 'app/entities/submission.model'; import { TranslateDirective } from 'app/shared/language/translate.directive'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { MockComponent, MockDirective, MockPipe } from 'ng-mocks'; -import { ExamResultSummaryExerciseCardHeaderComponent } from 'app/exam/participate/summary/exercises/header/exam-result-summary-exercise-card-header.component'; -import { ResultSummaryExerciseInfo } from 'app/exam/participate/summary/exam-result-summary.component'; import { input } from '@angular/core'; import { ExamResultSummaryExerciseCardHeaderComponent } from 'app/exam/overview/summary/exercises/header/exam-result-summary-exercise-card-header.component'; import { ResultSummaryExerciseInfo } from 'app/exam/overview/summary/exam-result-summary.component'; From 4133b9009875ff0f0a88fee475174d4ecda124e9 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Tue, 25 Mar 2025 12:59:45 +0100 Subject: [PATCH 30/49] remove redundant brackets --- .../overview/exam-cover/exam-participation-cover.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/app/exam/overview/exam-cover/exam-participation-cover.component.html b/src/main/webapp/app/exam/overview/exam-cover/exam-participation-cover.component.html index ec27571eae96..1ef83c95a648 100644 --- a/src/main/webapp/app/exam/overview/exam-cover/exam-participation-cover.component.html +++ b/src/main/webapp/app/exam/overview/exam-cover/exam-participation-cover.component.html @@ -3,7 +3,7 @@ -->
@if (startView()) {
From 8f1743d733f78732d40daf652e4f6514dfa8b258 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Tue, 25 Mar 2025 13:34:18 +0100 Subject: [PATCH 31/49] fix exam-start-information.component.spec.ts --- .../exam-start-information.component.spec.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/test/javascript/spec/component/exam/participate/exam-start-information/exam-start-information.component.spec.ts b/src/test/javascript/spec/component/exam/participate/exam-start-information/exam-start-information.component.spec.ts index 972638ec0b0e..aa56fde46a7f 100644 --- a/src/test/javascript/spec/component/exam/participate/exam-start-information/exam-start-information.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/exam-start-information/exam-start-information.component.spec.ts @@ -162,8 +162,10 @@ describe('ExamStartInformationComponent', () => { it('should initialize start date of the test exam correctly', () => { const examStartDate = dayjs('2022-02-06 02:00:00'); exam.testExam = true; - component.exam = exam; - component.studentExam = studentExam; + TestBed.runInInjectionContext(() => { + component.exam = input(exam); + component.studentExam = input(studentExam); + }); fixture.detectChanges(); expect(component.startDate).toStrictEqual(examStartDate); }); @@ -171,8 +173,10 @@ describe('ExamStartInformationComponent', () => { it('should initialize end date of the test exam correctly', () => { const examEndDate = dayjs('2022-02-06 02:00:00').add(1, 'hours'); exam.testExam = true; - component.exam = exam; - component.studentExam = studentExam; + TestBed.runInInjectionContext(() => { + component.exam = input(exam); + component.studentExam = input(studentExam); + }); fixture.detectChanges(); expect(component.endDate).toStrictEqual(examEndDate); }); From 11c552f274a59c2f3a2aaa64b085302c75e436d6 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Tue, 25 Mar 2025 23:07:30 +0100 Subject: [PATCH 32/49] fix tests --- .../exam-navigation-bar.component.spec.ts | 16 ++++++++-------- .../exam-navigation-sidebar.component.spec.ts | 13 +++++++------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/test/javascript/spec/component/exam/participate/exam-navigation-bar.component.spec.ts b/src/test/javascript/spec/component/exam/participate/exam-navigation-bar.component.spec.ts index 9e4fe181fd84..64ab3476fa30 100644 --- a/src/test/javascript/spec/component/exam/participate/exam-navigation-bar.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/exam-navigation-bar.component.spec.ts @@ -23,14 +23,14 @@ describe('Exam Navigation Bar Component', () => { let fixture: ComponentFixture; let comp: ExamNavigationBarComponent; let repositoryService: CodeEditorRepositoryService; - - const examExerciseIdForNavigationSourceMock = new BehaviorSubject(-1); - const mockExamExerciseUpdateService = { - currentExerciseIdForNavigation: examExerciseIdForNavigationSourceMock.asObservable(), - }; - - beforeEach(() => { - TestBed.configureTestingModule({ + let examExerciseIdForNavigationSourceMock: BehaviorSubject; + + beforeEach(async () => { + examExerciseIdForNavigationSourceMock = new BehaviorSubject(-1); + const mockExamExerciseUpdateService = { + currentExerciseIdForNavigation: examExerciseIdForNavigationSourceMock.asObservable(), + }; + await TestBed.configureTestingModule({ providers: [ { provide: ExamExerciseUpdateService, useValue: mockExamExerciseUpdateService }, { provide: LocalStorageService, useClass: MockSyncStorage }, diff --git a/src/test/javascript/spec/component/exam/participate/exam-navigation-sidebar.component.spec.ts b/src/test/javascript/spec/component/exam/participate/exam-navigation-sidebar.component.spec.ts index 5caec5181f29..ec02e6e1509f 100644 --- a/src/test/javascript/spec/component/exam/participate/exam-navigation-sidebar.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/exam-navigation-sidebar.component.spec.ts @@ -25,14 +25,15 @@ describe('ExamNavigationSidebarComponent', () => { let fixture: ComponentFixture; let comp: ExamNavigationSidebarComponent; let repositoryService: CodeEditorRepositoryService; + let examExerciseIdForNavigationSourceMock: BehaviorSubject; - const examExerciseIdForNavigationSourceMock = new BehaviorSubject(-1); - const mockExamExerciseUpdateService = { - currentExerciseIdForNavigation: examExerciseIdForNavigationSourceMock.asObservable(), - }; + beforeEach(async () => { + examExerciseIdForNavigationSourceMock = new BehaviorSubject(-1); + const mockExamExerciseUpdateService = { + currentExerciseIdForNavigation: examExerciseIdForNavigationSourceMock.asObservable(), + }; - beforeEach(() => { - TestBed.configureTestingModule({ + await TestBed.configureTestingModule({ providers: [ { provide: ExamExerciseUpdateService, useValue: mockExamExerciseUpdateService }, { provide: LocalStorageService, useClass: MockLocalStorageService }, From 4b759550609f0796c7f7c85a6038f562b89e12e8 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Tue, 25 Mar 2025 23:30:28 +0100 Subject: [PATCH 33/49] migrate last input --- .../exam-result-summary.component.html | 16 ++-- .../summary/exam-result-summary.component.ts | 80 +++++++++---------- 2 files changed, 46 insertions(+), 50 deletions(-) diff --git a/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.html b/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.html index 005b681f49d8..65bf77fc5ebc 100644 --- a/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.html +++ b/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.html @@ -9,21 +9,21 @@

-@if (studentExam && !studentExam.submitted) { +@if (studentExam() && !studentExam().submitted) {
} -@if (studentExam?.exam) { +@if (studentExam()?.exam) {
} -@if (studentExam && studentExam.exercises && studentExam.exam?.course && studentExamGradeInfoDTO) { +@if (studentExam() && studentExam().exercises && studentExam().exam?.course && studentExamGradeInfoDTO) {
}

-@for (exercise of studentExam?.exercises; track exercise; let i = $index) { +@for (exercise of studentExam()?.exercises; track exercise; let i = $index) {
@@ -140,7 +140,7 @@

@case (QUIZ) { diff --git a/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts b/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts index baffd6efdb5e..ef5785742ec6 100644 --- a/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts +++ b/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit, inject, input } from '@angular/core'; +import { Component, OnInit, effect, inject, input } from '@angular/core'; import { StudentExam } from 'app/entities/student-exam.model'; import { Exercise, ExerciseType, IncludedInOverallScore, getIcon } from 'app/entities/exercise.model'; import dayjs from 'dayjs/esm'; @@ -106,6 +106,8 @@ export class ExamResultSummaryComponent implements OnInit { readonly IncludedInOverallScore = IncludedInOverallScore; readonly PlagiarismVerdict = PlagiarismVerdict; + studentExam = input.required(); + faFolderOpen = faFolderOpen; faInfoCircle = faInfoCircle; faPrint = faPrint; @@ -113,27 +115,9 @@ export class ExamResultSummaryComponent implements OnInit { faEyeSlash = faEyeSlash; faArrowUp = faArrowUp; - /** - * Current student's exam. - */ - private _studentExam: StudentExam; - plagiarismCaseInfos: { [exerciseId: number]: PlagiarismCaseInfo } = {}; exampleSolutionPublished = false; - get studentExam(): StudentExam { - return this._studentExam; - } - - @Input() - set studentExam(studentExam: StudentExam) { - this._studentExam = studentExam; - if (this.studentExamGradeInfoDTO) { - this.studentExamGradeInfoDTO.studentExam = studentExam; - } - this.tryLoadPlagiarismCaseInfosForStudent(); - } - /** * Grade info for current student's exam. */ @@ -181,31 +165,31 @@ export class ExamResultSummaryComponent implements OnInit { ngOnInit(): void { // flags required to display test runs correctly this.isTestRun = this.route.snapshot.url[1]?.toString() === 'test-runs'; - this.isTestExam = this.studentExam.exam!.testExam!; + this.isTestExam = this.studentExam().exam!.testExam!; this.testRunConduction = this.isTestRun && this.route.snapshot.url[3]?.toString() === 'conduction'; - this.testExamConduction = this.isTestExam && !this.studentExam.submitted; + this.testExamConduction = this.isTestExam && !this.studentExam().submitted; this.courseId = Number(this.route.snapshot?.paramMap?.get('courseId') || this.route.parent?.parent?.snapshot.paramMap.get('courseId')); - if (!this.studentExam?.id) { + if (!this.studentExam().id) { throw new Error('studentExam.id should be present to fetch grade info'); } - if (!this.studentExam?.exam?.id) { + if (!this.studentExam().exam?.id) { throw new Error('studentExam.exam.id should be present to fetch grade info'); } - if (!this.studentExam?.user?.id) { + if (!this.studentExam().user?.id) { throw new Error('studentExam.user.id should be present to fetch grade info'); } - if (isExamResultPublished(this.isTestRun, this.studentExam.exam, this.serverDateService)) { + if (isExamResultPublished(this.isTestRun, this.studentExam().exam, this.serverDateService)) { this.examParticipationService - .loadStudentExamGradeInfoForSummary(this.courseId, this.studentExam.exam.id, this.studentExam.id, this.studentExam.user.id) + .loadStudentExamGradeInfoForSummary(this.courseId, this.studentExam().exam!.id!, this.studentExam().id!, this.studentExam().user!.id!) .subscribe((studentExamWithGrade: StudentExamWithGradeDTO) => { - studentExamWithGrade.studentExam = this.studentExam; + studentExamWithGrade.studentExam = this.studentExam(); this.studentExamGradeInfoDTO = studentExamWithGrade; this.exerciseInfos = this.getExerciseInfos(studentExamWithGrade); }); } - this.exampleSolutionPublished = !!this.studentExam.exam?.exampleSolutionPublicationDate && dayjs().isAfter(this.studentExam.exam.exampleSolutionPublicationDate); + this.exampleSolutionPublished = !!this.studentExam().exam?.exampleSolutionPublicationDate && dayjs().isAfter(this.studentExam().exam?.exampleSolutionPublicationDate); this.exerciseInfos = this.getExerciseInfos(); @@ -215,6 +199,18 @@ export class ExamResultSummaryComponent implements OnInit { this.isAfterStudentReviewStart = this.getIsAfterStudentReviewStart(); } + constructor() { + effect(() => { + const exam = this.studentExam(); + + // replicate the setter logic + if (this.studentExamGradeInfoDTO) { + this.studentExamGradeInfoDTO.studentExam = exam; + } + this.tryLoadPlagiarismCaseInfosForStudent(); + }); + } + get resultsArePublished(): boolean | any { if (this.isTestRun || this.isTestExam) { return true; @@ -224,8 +220,8 @@ export class ExamResultSummaryComponent implements OnInit { return false; } - if (this.studentExam?.exam?.publishResultsDate) { - return dayjs(this.studentExam.exam.publishResultsDate).isBefore(dayjs()); + if (this.studentExam().exam?.publishResultsDate) { + return dayjs(this.studentExam().exam?.publishResultsDate).isBefore(dayjs()); } return false; @@ -234,14 +230,14 @@ export class ExamResultSummaryComponent implements OnInit { private tryLoadPlagiarismCaseInfosForStudent() { // If the exam has not yet ended, or we're only a few minutes after the end, we can assume that there are no plagiarism cases yet. // We should avoid trying to load them to reduce server load. - if (this.studentExam?.exam?.endDate) { - const endDateWithTimeExtension = dayjs(this.studentExam.exam.endDate).add(2, 'hours'); + if (this.studentExam().exam?.endDate) { + const endDateWithTimeExtension = dayjs(this.studentExam().exam?.endDate).add(2, 'hours'); if (dayjs().isBefore(endDateWithTimeExtension)) { return; } } - const exerciseIds = this.studentExam?.exercises?.map((exercise) => exercise.id!); + const exerciseIds = this.studentExam().exercises?.map((exercise) => exercise.id!); if (exerciseIds?.length && this.courseId) { this.plagiarismCasesService.getPlagiarismCaseInfosForStudent(this.courseId, exerciseIds).subscribe((res) => { this.plagiarismCaseInfos = res.body ?? {}; @@ -335,10 +331,10 @@ export class ExamResultSummaryComponent implements OnInit { */ setExamWithOnlyIdAndStudentReviewPeriod() { const exam = new Exam(); - exam.id = this.studentExam?.exam?.id; - exam.examStudentReviewStart = this.studentExam?.exam?.examStudentReviewStart; - exam.examStudentReviewEnd = this.studentExam?.exam?.examStudentReviewEnd; - exam.course = this.studentExam?.exam?.course; + exam.id = this.studentExam().exam?.id; + exam.examStudentReviewStart = this.studentExam().exam?.examStudentReviewStart; + exam.examStudentReviewEnd = this.studentExam().exam?.examStudentReviewEnd; + exam.course = this.studentExam().exam?.course; this.examWithOnlyIdAndStudentReviewPeriod = exam; } @@ -346,8 +342,8 @@ export class ExamResultSummaryComponent implements OnInit { if (this.isTestRun || this.isTestExam) { return true; } - if (this.studentExam?.exam?.examStudentReviewStart && this.studentExam.exam.examStudentReviewEnd) { - return this.serverDateService.now().isAfter(this.studentExam.exam.examStudentReviewStart); + if (this.studentExam().exam?.examStudentReviewStart && this.studentExam().exam?.examStudentReviewEnd) { + return this.serverDateService.now().isAfter(this.studentExam().exam!.examStudentReviewStart); } return false; } @@ -356,15 +352,15 @@ export class ExamResultSummaryComponent implements OnInit { if (this.isTestRun || this.isTestExam) { return true; } - if (this.studentExam?.exam?.examStudentReviewStart && this.studentExam.exam.examStudentReviewEnd) { - return this.serverDateService.now().isBefore(this.studentExam.exam.examStudentReviewEnd); + if (this.studentExam().exam?.examStudentReviewStart && this.studentExam().exam?.examStudentReviewEnd) { + return this.serverDateService.now().isBefore(this.studentExam().exam!.examStudentReviewEnd); } return false; } private getExerciseInfos(studentExamWithGrade?: StudentExamWithGradeDTO): Record { const exerciseInfos: Record = {}; - for (const exercise of this.studentExam?.exercises ?? []) { + for (const exercise of this.studentExam().exercises ?? []) { if (exercise.id === undefined) { this.alertService.error('artemisApp.exam.error.cannotDisplayExerciseDetails', { exerciseGroupTitle: exercise.exerciseGroup?.title }); const errorMessage = 'Cannot getExerciseInfos as exerciseId is undefined'; From 45eef41891f60d0485c187b23420d0e0a313ae81 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Tue, 25 Mar 2025 23:40:07 +0100 Subject: [PATCH 34/49] fix exam-result-summary.component.spec.ts --- .../exam-result-summary.component.spec.ts | 67 +++++++++++++------ 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts b/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts index 71517b8d7f22..5da7e4db20c4 100644 --- a/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts @@ -40,6 +40,7 @@ import { provideHttpClientTesting } from '@angular/common/http/testing'; import { MockTranslateService } from '../../../../helpers/mocks/service/mock-translate.service'; import { TranslateService } from '@ngx-translate/core'; import { MockSyncStorage } from '../../../../helpers/mocks/service/mock-sync-storage.service'; +import { input } from '@angular/core'; let fixture: ComponentFixture; let component: ExamResultSummaryComponent; @@ -200,7 +201,9 @@ function sharedSetup(url: string[]) { .then(() => { fixture = TestBed.createComponent(ExamResultSummaryComponent); component = fixture.componentInstance; - component.studentExam = studentExam; + TestBed.runInInjectionContext(() => { + component.studentExam = input(studentExam); + }); artemisServerDateService = TestBed.inject(ArtemisServerDateService); examParticipationService = TestBed.inject(ExamParticipationService); }); @@ -244,7 +247,7 @@ describe('ExamResultSummaryComponent', () => { fixture.detectChanges(); const courseId = 1; - expect(component.studentExam).toEqual(studentExam); + expect(component.studentExam()).toEqual(studentExam); expect(serviceSpy).toHaveBeenCalledOnce(); expect(serviceSpy).toHaveBeenCalledWith(courseId, studentExam.exam!.id, studentExam.id, studentExam.user!.id); expect(component.studentExamGradeInfoDTO).toEqual({ ...gradeInfo, studentExam }); @@ -302,42 +305,54 @@ describe('ExamResultSummaryComponent', () => { component.courseId = courseId; const studentExam2 = { id: 2 } as StudentExam; - component.studentExam = studentExam2; + TestBed.runInInjectionContext(() => { + component.studentExam = input(studentExam2); + }); expect(component.studentExamGradeInfoDTO).toBeUndefined(); - expect(component.studentExam.id).toBe(studentExam2.id); + expect(component.studentExam().id).toBe(studentExam2.id); expect(plagiarismServiceSpy).not.toHaveBeenCalled(); - component.studentExam = studentExam; + TestBed.runInInjectionContext(() => { + component.studentExam = input(studentExam); + }); fixture.detectChanges(); - expect(component.studentExam).toEqual(studentExam); + expect(component.studentExam()).toEqual(studentExam); expect(component.studentExamGradeInfoDTO.studentExam).toEqual(studentExam); - expect(component.studentExam.id).toBe(studentExam.id); + expect(component.studentExam().id).toBe(studentExam.id); expect(plagiarismServiceSpy).toHaveBeenCalledOnce(); expect(plagiarismServiceSpy).toHaveBeenCalledWith(courseId, [1, 2, 3, 4]); const studentExam3 = { id: 3 } as StudentExam; - component.studentExam = studentExam3; + TestBed.runInInjectionContext(() => { + component.studentExam = input(studentExam3); + }); expect(component.studentExamGradeInfoDTO.studentExam).toEqual(studentExam3); - expect(component.studentExam.id).toBe(studentExam3.id); + expect(component.studentExam().id).toBe(studentExam3.id); expect(plagiarismServiceSpy).toHaveBeenCalledOnce(); }); it('should correctly identify a TestExam', () => { - component.studentExam = studentExamForTestExam; + TestBed.runInInjectionContext(() => { + component.studentExam = input(studentExamForTestExam); + }); component.ngOnInit(); expect(component.isTestExam).toBeTrue(); expect(component.testExamConduction).toBeTrue(); studentExamForTestExam.submitted = true; - component.studentExam = studentExamForTestExam; + TestBed.runInInjectionContext(() => { + component.studentExam = input(studentExamForTestExam); + }); component.ngOnInit(); expect(component.isTestExam).toBeTrue(); expect(component.testExamConduction).toBeFalse(); }); it('should correctly identify a RealExam', () => { - component.studentExam = studentExam; + TestBed.runInInjectionContext(() => { + component.studentExam = input(studentExam); + }); component.ngOnInit(); expect(component.isTestExam).toBeFalse(); expect(component.testExamConduction).toBeFalse(); @@ -345,7 +360,9 @@ describe('ExamResultSummaryComponent', () => { expect(component.testRunConduction).toBeFalse(); studentExam.submitted = true; - component.studentExam = studentExam; + TestBed.runInInjectionContext(() => { + component.studentExam = input(studentExam); + }); component.ngOnInit(); expect(component.isTestExam).toBeFalse(); expect(component.testExamConduction).toBeFalse(); @@ -354,7 +371,9 @@ describe('ExamResultSummaryComponent', () => { }); it('should correctly determine if the results are published', () => { - component.studentExam = studentExam; + TestBed.runInInjectionContext(() => { + component.studentExam = input(studentExam); + }); component.testRunConduction = true; expect(component.resultsArePublished).toBeFalse(); @@ -374,12 +393,14 @@ describe('ExamResultSummaryComponent', () => { // const publishResultsDate is in the past expect(component.resultsArePublished).toBeTrue(); - component.studentExam.exam!.publishResultsDate = dayjs().add(2, 'hours'); + component.studentExam().exam!.publishResultsDate = dayjs().add(2, 'hours'); expect(component.resultsArePublished).toBeFalse(); }); it('should load exam summary when results are published', () => { - component.studentExam = studentExam; + TestBed.runInInjectionContext(() => { + component.studentExam = input(studentExam); + }); const loadStudentExamGradeInfoForSummarySpy = jest.spyOn(examParticipationService, 'loadStudentExamGradeInfoForSummary'); const isExamResultPublishedSpy = jest.spyOn(ExamUtils, 'isExamResultPublished').mockReturnValue(true); @@ -403,12 +424,12 @@ describe('ExamResultSummaryComponent', () => { expect(component.isAfterStudentReviewStart).toBeTrue(); component.isTestRun = false; - component.studentExam.exam!.examStudentReviewStart = examStudentReviewStart; - component.studentExam.exam!.examStudentReviewEnd = examStudentReviewEnd; + component.studentExam().exam!.examStudentReviewStart = examStudentReviewStart; + component.studentExam().exam!.examStudentReviewEnd = examStudentReviewEnd; component.ngOnInit(); expect(component.isAfterStudentReviewStart).toBeTrue(); - component.studentExam.exam!.examStudentReviewStart = dayjs().add(30, 'minutes'); + component.studentExam().exam!.examStudentReviewStart = dayjs().add(30, 'minutes'); component.ngOnInit(); expect(component.isAfterStudentReviewStart).toBeFalse(); @@ -429,11 +450,11 @@ describe('ExamResultSummaryComponent', () => { expect(component.isBeforeStudentReviewEnd).toBeTrue(); component.isTestRun = false; - component.studentExam.exam!.examStudentReviewEnd = examStudentReviewEnd; + component.studentExam().exam!.examStudentReviewEnd = examStudentReviewEnd; component.ngOnInit(); expect(component.isBeforeStudentReviewEnd).toBeTrue(); - component.studentExam.exam!.examStudentReviewEnd = dayjs().subtract(30, 'minutes'); + component.studentExam().exam!.examStudentReviewEnd = dayjs().subtract(30, 'minutes'); component.ngOnInit(); expect(component.isBeforeStudentReviewEnd).toBeFalse(); @@ -527,7 +548,9 @@ describe('ExamResultSummaryComponent', () => { scrollIntoView: scrollIntoViewSpy, } as unknown as HTMLElement); - component.studentExam = studentExam; + TestBed.runInInjectionContext(() => { + component.studentExam = input(studentExam); + }); component.studentExamGradeInfoDTO = { ...gradeInfo, studentExam }; fixture.detectChanges(); From 82e8515783820b5a6494ab886e012fe5fc9a72e1 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Wed, 26 Mar 2025 11:17:22 +0100 Subject: [PATCH 35/49] fix exam-result-summary.component.spec.ts --- .../summary/exam-result-summary.component.ts | 3 +- .../exam-result-summary.component.spec.ts | 53 ++++++------------- 2 files changed, 17 insertions(+), 39 deletions(-) diff --git a/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts b/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts index 0a0a60bdc1ea..a0ff9556d6d5 100644 --- a/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts +++ b/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts @@ -106,8 +106,6 @@ export class ExamResultSummaryComponent implements OnInit { readonly IncludedInOverallScore = IncludedInOverallScore; readonly PlagiarismVerdict = PlagiarismVerdict; - studentExam = input.required(); - faFolderOpen = faFolderOpen; faInfoCircle = faInfoCircle; faPrint = faPrint; @@ -127,6 +125,7 @@ export class ExamResultSummaryComponent implements OnInit { isBonusGradingKeyCollapsed = true; instructorView = input(false); + studentExam = input.required(); courseId: number; diff --git a/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts b/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts index dd2e241e8521..edc1a7428c9c 100644 --- a/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts @@ -40,7 +40,6 @@ import { provideHttpClientTesting } from '@angular/common/http/testing'; import { MockTranslateService } from '../../../../helpers/mocks/service/mock-translate.service'; import { TranslateService } from '@ngx-translate/core'; import { MockSyncStorage } from '../../../../helpers/mocks/service/mock-sync-storage.service'; -import { input } from '@angular/core'; let fixture: ComponentFixture; let component: ExamResultSummaryComponent; @@ -201,9 +200,7 @@ function sharedSetup(url: string[]) { .then(() => { fixture = TestBed.createComponent(ExamResultSummaryComponent); component = fixture.componentInstance; - TestBed.runInInjectionContext(() => { - component.studentExam = input(studentExam); - }); + fixture.componentRef.setInput('studentExam', studentExam); artemisServerDateService = TestBed.inject(ArtemisServerDateService); examParticipationService = TestBed.inject(ExamParticipationService); }); @@ -301,22 +298,18 @@ describe('ExamResultSummaryComponent', () => { const plagiarismService = fixture.debugElement.injector.get(PlagiarismCasesService); const plagiarismServiceSpy = jest.spyOn(plagiarismService, 'getPlagiarismCaseInfosForStudent'); - const courseId = 10; - component.courseId = courseId; + const courseId = 1; const studentExam2 = { id: 2 } as StudentExam; - TestBed.runInInjectionContext(() => { - component.studentExam = input(studentExam2); - }); + fixture.componentRef.setInput('studentExam', studentExam2); + expect(component.studentExamGradeInfoDTO).toBeUndefined(); expect(component.studentExam().id).toBe(studentExam2.id); expect(plagiarismServiceSpy).not.toHaveBeenCalled(); - TestBed.runInInjectionContext(() => { - component.studentExam = input(studentExam); - }); - fixture.detectChanges(); + fixture.componentRef.setInput('studentExam', studentExam); + fixture.detectChanges(); expect(component.studentExam()).toEqual(studentExam); expect(component.studentExamGradeInfoDTO.studentExam).toEqual(studentExam); expect(component.studentExam().id).toBe(studentExam.id); @@ -324,35 +317,29 @@ describe('ExamResultSummaryComponent', () => { expect(plagiarismServiceSpy).toHaveBeenCalledWith(courseId, [1, 2, 3, 4]); const studentExam3 = { id: 3 } as StudentExam; - TestBed.runInInjectionContext(() => { - component.studentExam = input(studentExam3); - }); + fixture.componentRef.setInput('studentExam', studentExam3); + fixture.detectChanges(); + expect(component.studentExamGradeInfoDTO.studentExam).toEqual(studentExam3); expect(component.studentExam().id).toBe(studentExam3.id); expect(plagiarismServiceSpy).toHaveBeenCalledOnce(); }); it('should correctly identify a TestExam', () => { - TestBed.runInInjectionContext(() => { - component.studentExam = input(studentExamForTestExam); - }); + fixture.componentRef.setInput('studentExam', studentExamForTestExam); component.ngOnInit(); expect(component.isTestExam).toBeTrue(); expect(component.testExamConduction).toBeTrue(); studentExamForTestExam.submitted = true; - TestBed.runInInjectionContext(() => { - component.studentExam = input(studentExamForTestExam); - }); + fixture.componentRef.setInput('studentExam', studentExamForTestExam); component.ngOnInit(); expect(component.isTestExam).toBeTrue(); expect(component.testExamConduction).toBeFalse(); }); it('should correctly identify a RealExam', () => { - TestBed.runInInjectionContext(() => { - component.studentExam = input(studentExam); - }); + fixture.componentRef.setInput('studentExam', studentExam); component.ngOnInit(); expect(component.isTestExam).toBeFalse(); expect(component.testExamConduction).toBeFalse(); @@ -360,9 +347,7 @@ describe('ExamResultSummaryComponent', () => { expect(component.testRunConduction).toBeFalse(); studentExam.submitted = true; - TestBed.runInInjectionContext(() => { - component.studentExam = input(studentExam); - }); + fixture.componentRef.setInput('studentExam', studentExam); component.ngOnInit(); expect(component.isTestExam).toBeFalse(); expect(component.testExamConduction).toBeFalse(); @@ -371,9 +356,7 @@ describe('ExamResultSummaryComponent', () => { }); it('should correctly determine if the results are published', () => { - TestBed.runInInjectionContext(() => { - component.studentExam = input(studentExam); - }); + fixture.componentRef.setInput('studentExam', studentExam); component.testRunConduction = true; expect(component.resultsArePublished).toBeFalse(); @@ -398,9 +381,7 @@ describe('ExamResultSummaryComponent', () => { }); it('should load exam summary when results are published', () => { - TestBed.runInInjectionContext(() => { - component.studentExam = input(studentExam); - }); + fixture.componentRef.setInput('studentExam', studentExam); const loadStudentExamGradeInfoForSummarySpy = jest.spyOn(examParticipationService, 'loadStudentExamGradeInfoForSummary'); const isExamResultPublishedSpy = jest.spyOn(ExamUtils, 'isExamResultPublished').mockReturnValue(true); @@ -548,9 +529,7 @@ describe('ExamResultSummaryComponent', () => { scrollIntoView: scrollIntoViewSpy, } as unknown as HTMLElement); - TestBed.runInInjectionContext(() => { - component.studentExam = input(studentExam); - }); + fixture.componentRef.setInput('studentExam', studentExam); component.studentExamGradeInfoDTO = { ...gradeInfo, studentExam }; fixture.detectChanges(); From 5d19016645389eb4bee3ffea6cf938251e24d5c2 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Wed, 26 Mar 2025 13:11:43 +0100 Subject: [PATCH 36/49] remove redundant comment --- .../app/exam/overview/summary/exam-result-summary.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts b/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts index a0ff9556d6d5..320ab7ac032e 100644 --- a/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts +++ b/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts @@ -202,7 +202,6 @@ export class ExamResultSummaryComponent implements OnInit { effect(() => { const exam = this.studentExam(); - // replicate the setter logic if (this.studentExamGradeInfoDTO) { this.studentExamGradeInfoDTO.studentExam = exam; } From 0d45d7359d0e3e4219c5f7661a55c7ff409d87c4 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Thu, 27 Mar 2025 16:03:20 +0100 Subject: [PATCH 37/49] resolve conflicts --- .../general-information/exam-general-information.component.ts | 2 +- .../app/exam/overview/summary/exam-result-summary.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/app/exam/overview/general-information/exam-general-information.component.ts b/src/main/webapp/app/exam/overview/general-information/exam-general-information.component.ts index 75d49e286013..8572c05277f3 100644 --- a/src/main/webapp/app/exam/overview/general-information/exam-general-information.component.ts +++ b/src/main/webapp/app/exam/overview/general-information/exam-general-information.component.ts @@ -1,4 +1,4 @@ -import { Component, input, OnChanges } from '@angular/core'; +import { Component, OnChanges, input } from '@angular/core'; import { StudentExam } from 'app/exam/shared/entities/student-exam.model'; import { Exam } from 'app/exam/shared/entities/exam.model'; import { endTime, examWorkingTime, getAdditionalWorkingTime, isExamOverMultipleDays } from 'app/exam/overview/exam.utils'; diff --git a/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts b/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts index 8919ddba7d2f..bcb356ed85d7 100644 --- a/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts +++ b/src/main/webapp/app/exam/overview/summary/exam-result-summary.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, inject, input } from '@angular/core'; +import { Component, OnInit, effect, inject, input } from '@angular/core'; import { StudentExam } from 'app/exam/shared/entities/student-exam.model'; import { Exercise, ExerciseType, IncludedInOverallScore, getIcon } from 'app/exercise/shared/entities/exercise/exercise.model'; import dayjs from 'dayjs/esm'; From 3aea8795cb0139273fc8f02f39844af351ebffc9 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Thu, 27 Mar 2025 22:18:50 +0100 Subject: [PATCH 38/49] fix imports --- .../exercises/file-upload-exam-summary.component.spec.ts | 2 +- .../summary/exercises/modeling-exam-summary.component.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/javascript/spec/component/exam/participate/summary/exercises/file-upload-exam-summary.component.spec.ts b/src/test/javascript/spec/component/exam/participate/summary/exercises/file-upload-exam-summary.component.spec.ts index 08697fb6cd48..182f1dc935a0 100644 --- a/src/test/javascript/spec/component/exam/participate/summary/exercises/file-upload-exam-summary.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/summary/exercises/file-upload-exam-summary.component.spec.ts @@ -15,7 +15,7 @@ import { MockProvider } from 'ng-mocks'; import { MockTranslateService } from '../../../../../helpers/mocks/service/mock-translate.service'; import { TranslateService } from '@ngx-translate/core'; import { input } from '@angular/core'; -import { Exercise } from 'app/entities/exercise.model'; +import { Exercise } from 'app/exercise/shared/entities/exercise/exercise.model'; describe('FileUploadExamSummaryComponent', () => { let fixture: ComponentFixture; diff --git a/src/test/javascript/spec/component/exam/participate/summary/exercises/modeling-exam-summary.component.spec.ts b/src/test/javascript/spec/component/exam/participate/summary/exercises/modeling-exam-summary.component.spec.ts index caa719bc113c..d6e644a7c839 100644 --- a/src/test/javascript/spec/component/exam/participate/summary/exercises/modeling-exam-summary.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/summary/exercises/modeling-exam-summary.component.spec.ts @@ -19,8 +19,8 @@ import { MockProfileService } from '../../../../../helpers/mocks/service/mock-pr import { MockTranslateService } from '../../../../../helpers/mocks/service/mock-translate.service'; import { TranslateService } from '@ngx-translate/core'; import { input } from '@angular/core'; -import { Exercise } from 'app/entities/exercise.model'; -import { Submission } from 'app/entities/submission.model'; +import { Exercise } from 'app/exercise/shared/entities/exercise/exercise.model'; +import { Submission } from 'app/exercise/shared/entities/submission/submission.model'; describe('ModelingExamSummaryComponent', () => { let fixture: ComponentFixture; From cbe482e014a4ba7268d827b670012bd34c3d05df Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Tue, 22 Apr 2025 00:57:21 +0200 Subject: [PATCH 39/49] resolve conflict --- .../events/button/exam-live-events-button.component.spec.ts | 2 -- .../exam-navigation-sidebar.component.spec.ts | 1 - .../programming-exam-summary.component.spec.ts | 2 -- .../text-exam-summary/text-exam-summary.component.spec.ts | 1 - .../result-overview/exam-result-overview.component.spec.ts | 1 - 5 files changed, 7 deletions(-) diff --git a/src/main/webapp/app/exam/overview/events/button/exam-live-events-button.component.spec.ts b/src/main/webapp/app/exam/overview/events/button/exam-live-events-button.component.spec.ts index 306a7d10a86c..23afe9681a3b 100644 --- a/src/main/webapp/app/exam/overview/events/button/exam-live-events-button.component.spec.ts +++ b/src/main/webapp/app/exam/overview/events/button/exam-live-events-button.component.spec.ts @@ -6,10 +6,8 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { ExamLiveEvent, ExamLiveEventType, ExamParticipationLiveEventsService } from 'app/exam/overview/services/exam-participation-live-events.service'; import { of } from 'rxjs'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { MockExamParticipationLiveEventsService } from '../../../../helpers/mocks/service/mock-exam-participation-live-events.service'; import { input } from '@angular/core'; import dayjs from 'dayjs/esm'; -import { ExamLiveEventsOverlayComponent } from 'app/exam/overview/events/exam-live-events-overlay.component'; import { MockExamParticipationLiveEventsService } from 'test/helpers/mocks/service/mock-exam-participation-live-events.service'; import { ExamLiveEventsOverlayComponent } from 'app/exam/overview/events/overlay/exam-live-events-overlay.component'; diff --git a/src/main/webapp/app/exam/overview/exam-navigation-sidebar/exam-navigation-sidebar.component.spec.ts b/src/main/webapp/app/exam/overview/exam-navigation-sidebar/exam-navigation-sidebar.component.spec.ts index 14da1ed3023a..e1c66cf50f6c 100644 --- a/src/main/webapp/app/exam/overview/exam-navigation-sidebar/exam-navigation-sidebar.component.spec.ts +++ b/src/main/webapp/app/exam/overview/exam-navigation-sidebar/exam-navigation-sidebar.component.spec.ts @@ -17,7 +17,6 @@ import { provideHttpClientTesting } from '@angular/common/http/testing'; import { MockTranslateService } from 'test/helpers/mocks/service/mock-translate.service'; import { TranslateService } from '@ngx-translate/core'; import { ProfileService } from 'app/core/layouts/profiles/shared/profile.service'; -import { MockProfileService } from '../../../helpers/mocks/service/mock-profile.service'; import { input, model } from '@angular/core'; import { MockProfileService } from 'test/helpers/mocks/service/mock-profile.service'; import { facSaveSuccess, facSaveWarning } from 'app/shared/icons/icons'; diff --git a/src/main/webapp/app/exam/overview/summary/exercises/programming-exam-summary/programming-exam-summary.component.spec.ts b/src/main/webapp/app/exam/overview/summary/exercises/programming-exam-summary/programming-exam-summary.component.spec.ts index 612a7ff82839..510d52599d0c 100644 --- a/src/main/webapp/app/exam/overview/summary/exercises/programming-exam-summary/programming-exam-summary.component.spec.ts +++ b/src/main/webapp/app/exam/overview/summary/exercises/programming-exam-summary/programming-exam-summary.component.spec.ts @@ -1,5 +1,4 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ProfileService } from 'app/core/layouts/profiles/shared/profile.service'; import { ExerciseType } from 'app/exercise/shared/entities/exercise/exercise.model'; import { ProgrammingExamSummaryComponent } from 'app/exam/overview/summary/exercises/programming-exam-summary/programming-exam-summary.component'; import { CodeButtonComponent } from 'app/shared/components/code-button/code-button.component'; @@ -28,7 +27,6 @@ import { provideHttpClient } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import { input } from '@angular/core'; import { ProfileService } from 'app/core/layouts/profiles/shared/profile.service'; -import { ProfileInfo } from 'app/core/layouts/profiles/profile-info.model'; const user = { id: 1, name: 'Test User' } as User; diff --git a/src/main/webapp/app/exam/overview/summary/exercises/text-exam-summary/text-exam-summary.component.spec.ts b/src/main/webapp/app/exam/overview/summary/exercises/text-exam-summary/text-exam-summary.component.spec.ts index 3655fd1e854f..9f01dd4eee25 100644 --- a/src/main/webapp/app/exam/overview/summary/exercises/text-exam-summary/text-exam-summary.component.spec.ts +++ b/src/main/webapp/app/exam/overview/summary/exercises/text-exam-summary/text-exam-summary.component.spec.ts @@ -14,7 +14,6 @@ import { SessionStorageService } from 'ngx-webstorage'; import { MockSyncStorage } from 'test/helpers/mocks/service/mock-sync-storage.service'; import { ProfileService } from 'app/core/layouts/profiles/shared/profile.service'; import { MockProfileService } from 'test/helpers/mocks/service/mock-profile.service'; -import { MockProfileService } from '../../../../../helpers/mocks/service/mock-profile.service'; import { input } from '@angular/core'; describe('TextExamSummaryComponent', () => { diff --git a/src/main/webapp/app/exam/overview/summary/result-overview/exam-result-overview.component.spec.ts b/src/main/webapp/app/exam/overview/summary/result-overview/exam-result-overview.component.spec.ts index 7fdedbb6a64f..f5f26d3f49b5 100644 --- a/src/main/webapp/app/exam/overview/summary/result-overview/exam-result-overview.component.spec.ts +++ b/src/main/webapp/app/exam/overview/summary/result-overview/exam-result-overview.component.spec.ts @@ -24,7 +24,6 @@ import { MockTranslateService } from 'test/helpers/mocks/service/mock-translate. import { TranslateService } from '@ngx-translate/core'; import { AccountService } from 'app/core/auth/account.service'; import { MockAccountService } from 'test/helpers/mocks/service/mock-account.service'; -import { MockAccountService } from '../../../../../helpers/mocks/service/mock-account.service'; import { input } from '@angular/core'; let fixture: ComponentFixture; From 7697664cda474d9052eaf294a672008d0afed713 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Tue, 22 Apr 2025 18:40:47 +0200 Subject: [PATCH 40/49] fix programming-exam-summary.component.spec.ts --- ...programming-exam-summary.component.spec.ts | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/main/webapp/app/exam/overview/summary/exercises/programming-exam-summary/programming-exam-summary.component.spec.ts b/src/main/webapp/app/exam/overview/summary/exercises/programming-exam-summary/programming-exam-summary.component.spec.ts index 510d52599d0c..686f0d8fbc23 100644 --- a/src/main/webapp/app/exam/overview/summary/exercises/programming-exam-summary/programming-exam-summary.component.spec.ts +++ b/src/main/webapp/app/exam/overview/summary/exercises/programming-exam-summary/programming-exam-summary.component.spec.ts @@ -47,24 +47,6 @@ const programmingSubmission = { commitHash: '123456789ab', } as ProgrammingSubmission; -const programmingParticipation = { - id: 4, - student: user, - submissions: [programmingSubmission], - type: ParticipationType.PROGRAMMING, - participantIdentifier: 'student1', - repositoryUri: 'https://username@artemistest2.aet.cit.tum.de/FTCSCAGRADING1/ftcscagrading1-username', -} as ProgrammingExerciseStudentParticipation; - -const programmingExercise = { - id: 4, - type: ExerciseType.PROGRAMMING, - studentParticipations: [programmingParticipation], - exerciseGroup, - projectKey: 'TEST', - dueDate: dayjs().subtract(5, 'minutes'), -} as ProgrammingExercise; - const feedbackReference = { id: 1, result: { id: 2 } as Result, @@ -92,6 +74,25 @@ const result = { assessmentType: AssessmentType.MANUAL, } as Result; +const programmingParticipation = { + id: 4, + student: user, + submissions: [programmingSubmission], + type: ParticipationType.PROGRAMMING, + participantIdentifier: 'student1', + repositoryUri: 'https://username@artemistest2.aet.cit.tum.de/FTCSCAGRADING1/ftcscagrading1-username', + results: [result], +} as ProgrammingExerciseStudentParticipation; + +const programmingExercise = { + id: 4, + type: ExerciseType.PROGRAMMING, + studentParticipations: [programmingParticipation], + exerciseGroup, + projectKey: 'TEST', + dueDate: dayjs().subtract(5, 'minutes'), +} as ProgrammingExercise; + describe('ProgrammingExamSummaryComponent', () => { let component: ProgrammingExamSummaryComponent; let fixture: ComponentFixture; From 5cbd213cab0c497b6f58a5441fd30d9de5ae9e0b Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Fri, 25 Apr 2025 17:56:22 +0200 Subject: [PATCH 41/49] move inputs setup to beforeEach --- .../exam-start-information.component.spec.ts | 52 ++----------------- 1 file changed, 4 insertions(+), 48 deletions(-) diff --git a/src/main/webapp/app/exam/overview/exam-start-information/exam-start-information.component.spec.ts b/src/main/webapp/app/exam/overview/exam-start-information/exam-start-information.component.spec.ts index ae752582321e..cb2e8a252c68 100644 --- a/src/main/webapp/app/exam/overview/exam-start-information/exam-start-information.component.spec.ts +++ b/src/main/webapp/app/exam/overview/exam-start-information/exam-start-information.component.spec.ts @@ -54,6 +54,10 @@ describe('ExamStartInformationComponent', () => { .then(() => { fixture = TestBed.createComponent(ExamStartInformationComponent); component = fixture.componentInstance; + TestBed.runInInjectionContext(() => { + component.exam = input(exam); + component.studentExam = input(studentExam); + }); }); }); @@ -62,99 +66,59 @@ describe('ExamStartInformationComponent', () => { }); it('should initialize with the correct start date', () => { - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); expect(component.startDate).toEqual(exam.startDate); }); it('should return undefined if the exam is not set', () => { exam.startDate = undefined; - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); expect(component.startDate).toBeUndefined(); }); it('should initialize total points of the exam correctly', () => { exam.examMaxPoints = 120; - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); expect(component.totalPoints).toBe(120); }); it('should give total working time in minutes', () => { exam.workingTime = 60 * 60 * 2; - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); expect(component.totalWorkingTimeInMinutes).toBe(120); }); it('should initialize module number of the exam correctly', () => { exam.moduleNumber = 'IN18000'; - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); expect(component.moduleNumber).toBe('IN18000'); }); it('should initialize course name of the exam correctly', () => { exam.courseName = 'Software Engineering'; - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); expect(component.courseName).toBe('Software Engineering'); }); it('should initialize examiner of the exam correctly', () => { exam.examiner = 'Prof. Dr. Stephan Krusche'; - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); expect(component.examiner).toBe('Prof. Dr. Stephan Krusche'); }); it('should initialize number of exercises of the exam correctly', () => { exam.numberOfExercisesInExam = 10; - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); expect(component.numberOfExercisesInExam).toBe(10); }); it('should initialize examined student of the exam correctly', () => { - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); expect(component.examinedStudent).toBe('Test User'); }); it('should initialize start date of the exam correctly', () => { const examStartDate = dayjs('2022-02-06 02:00:00'); - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); expect(component.startDate).toStrictEqual(examStartDate); }); @@ -162,10 +126,6 @@ describe('ExamStartInformationComponent', () => { it('should initialize start date of the test exam correctly', () => { const examStartDate = dayjs('2022-02-06 02:00:00'); exam.testExam = true; - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); expect(component.startDate).toStrictEqual(examStartDate); }); @@ -173,10 +133,6 @@ describe('ExamStartInformationComponent', () => { it('should initialize end date of the test exam correctly', () => { const examEndDate = dayjs('2022-02-06 02:00:00').add(1, 'hours'); exam.testExam = true; - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); expect(component.endDate).toStrictEqual(examEndDate); }); From 10fe5e89e6b694606f983e201cd781358c45c681 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Fri, 25 Apr 2025 17:57:45 +0200 Subject: [PATCH 42/49] remove commented code --- .../exam-cover/exam-participation-cover.component.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/webapp/app/exam/overview/exam-cover/exam-participation-cover.component.spec.ts b/src/main/webapp/app/exam/overview/exam-cover/exam-participation-cover.component.spec.ts index 4ad92a0c9f7f..1f414e8f853c 100644 --- a/src/main/webapp/app/exam/overview/exam-cover/exam-participation-cover.component.spec.ts +++ b/src/main/webapp/app/exam/overview/exam-cover/exam-participation-cover.component.spec.ts @@ -200,7 +200,6 @@ describe('ExamParticipationCoverComponent', () => { it('should update displayed times if exam suddenly started', () => { component.testRun = true; component.exam().startDate = dayjs(); - // component.onExamStarted = new EventEmitter(); const eventSpy = jest.spyOn(component.onExamStarted, 'emit'); component.updateDisplayedTimes(studentExam); From 5171eb34490ba28b24e3b78300746fbf5f3dbe2a Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Fri, 25 Apr 2025 18:02:38 +0200 Subject: [PATCH 43/49] use set instead of update --- .../events/button/exam-live-events-button.component.ts | 2 +- .../exam-navigation-bar/exam-navigation-bar.component.ts | 4 ++-- .../exam-navigation-sidebar.component.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/webapp/app/exam/overview/events/button/exam-live-events-button.component.ts b/src/main/webapp/app/exam/overview/events/button/exam-live-events-button.component.ts index e98900ff7588..9300dd5d7c31 100644 --- a/src/main/webapp/app/exam/overview/events/button/exam-live-events-button.component.ts +++ b/src/main/webapp/app/exam/overview/events/button/exam-live-events-button.component.ts @@ -72,7 +72,7 @@ export class ExamLiveEventsButtonComponent implements OnInit, OnDestroy { windowClass: 'live-events-modal-window', }); - this.modalRef.componentInstance.examStartDate.update(() => this.examStartDate()); + this.modalRef.componentInstance.examStartDate.set(this.examStartDate()); from(this.modalRef.result).subscribe(() => (this.modalRef = undefined)); } diff --git a/src/main/webapp/app/exam/overview/exam-navigation-bar/exam-navigation-bar.component.ts b/src/main/webapp/app/exam/overview/exam-navigation-bar/exam-navigation-bar.component.ts index 7415de5a0985..246c5651e36d 100644 --- a/src/main/webapp/app/exam/overview/exam-navigation-bar/exam-navigation-bar.component.ts +++ b/src/main/webapp/app/exam/overview/exam-navigation-bar/exam-navigation-bar.component.ts @@ -142,11 +142,11 @@ export class ExamNavigationBarComponent implements OnInit, AfterViewInit { return; } // set index and emit event - this.exerciseIndex.update(() => exerciseIndex); + this.exerciseIndex.set(exerciseIndex); this.onPageChanged.emit({ overViewChange: false, exercise: this.exercises()[this.exerciseIndex()], forceSave: !!forceSave, submission: submission }); } else if (overviewPage) { // set index and emit event - this.exerciseIndex.update(() => -1); + this.exerciseIndex.set(-1); // save current exercise this.onPageChanged.emit({ overViewChange: true, exercise: undefined, forceSave: false }); } diff --git a/src/main/webapp/app/exam/overview/exam-navigation-sidebar/exam-navigation-sidebar.component.ts b/src/main/webapp/app/exam/overview/exam-navigation-sidebar/exam-navigation-sidebar.component.ts index e7ff008ddbaf..68856b2b635d 100644 --- a/src/main/webapp/app/exam/overview/exam-navigation-sidebar/exam-navigation-sidebar.component.ts +++ b/src/main/webapp/app/exam/overview/exam-navigation-sidebar/exam-navigation-sidebar.component.ts @@ -141,7 +141,7 @@ export class ExamNavigationSidebarComponent implements OnDestroy, OnInit { return; } // set index and emit event - this.exerciseIndex.update(() => exerciseIndex); + this.exerciseIndex.set(exerciseIndex); this.onPageChanged.emit({ overViewChange: false, exercise: this.exercises()[this.exerciseIndex()], @@ -150,7 +150,7 @@ export class ExamNavigationSidebarComponent implements OnDestroy, OnInit { }); } else if (overviewPage) { // set index and emit event - this.exerciseIndex.update(() => this.EXERCISE_OVERVIEW_INDEX); + this.exerciseIndex.set(this.EXERCISE_OVERVIEW_INDEX); // save current exercise this.onPageChanged.emit({ overViewChange: true, exercise: undefined, forceSave: false }); } From 4589cf648eec310eaeb6cfd1ed8db3b6dc451b28 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Sat, 26 Apr 2025 09:29:51 +0200 Subject: [PATCH 44/49] remove redundant element --- .../exam-navigation-bar/exam-navigation-bar.component.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/webapp/app/exam/overview/exam-navigation-bar/exam-navigation-bar.component.html b/src/main/webapp/app/exam/overview/exam-navigation-bar/exam-navigation-bar.component.html index 9cbfc548e987..68d3ec235c9a 100644 --- a/src/main/webapp/app/exam/overview/exam-navigation-bar/exam-navigation-bar.component.html +++ b/src/main/webapp/app/exam/overview/exam-navigation-bar/exam-navigation-bar.component.html @@ -18,9 +18,6 @@ @if (overviewPageOpen()) {
} -
- -
From d693002ec3b1515b3ebd51d9d3037e2d30d5c825 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Sat, 26 Apr 2025 09:30:11 +0200 Subject: [PATCH 45/49] make input parameter required --- .../button/exam-live-events-button.component.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main/webapp/app/exam/overview/events/button/exam-live-events-button.component.ts b/src/main/webapp/app/exam/overview/events/button/exam-live-events-button.component.ts index 9300dd5d7c31..30dfc3f7dff0 100644 --- a/src/main/webapp/app/exam/overview/events/button/exam-live-events-button.component.ts +++ b/src/main/webapp/app/exam/overview/events/button/exam-live-events-button.component.ts @@ -32,7 +32,7 @@ export class ExamLiveEventsButtonComponent implements OnInit, OnDestroy { private liveEventsSubscription?: Subscription; private allEventsSubscription?: Subscription; eventCount = 0; - examStartDate = input(); + examStartDate = input.required(); // Icons faBullhorn = faBullhorn; @@ -44,15 +44,12 @@ export class ExamLiveEventsButtonComponent implements OnInit, OnDestroy { this.eventCount = filteredEvents.length; }); - const examStartDate = this.examStartDate(); - if (examStartDate) { - this.liveEventsSubscription = this.liveEventsService.observeNewEventsAsUser(USER_DISPLAY_RELEVANT_EVENTS, examStartDate).subscribe(() => { - // If any unacknowledged event comes in, open the dialog to display it - if (!this.modalRef) { - this.openDialog(); - } - }); - } + this.liveEventsSubscription = this.liveEventsService.observeNewEventsAsUser(USER_DISPLAY_RELEVANT_EVENTS, this.examStartDate()).subscribe(() => { + // If any unacknowledged event comes in, open the dialog to display it + if (!this.modalRef) { + this.openDialog(); + } + }); } ngOnDestroy(): void { From 52baf3ba039f32c42dd871209a29e9ec4d95ba97 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Sat, 26 Apr 2025 09:41:49 +0200 Subject: [PATCH 46/49] make modal required --- .../overlay/exam-live-events-overlay.component.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/webapp/app/exam/overview/events/overlay/exam-live-events-overlay.component.ts b/src/main/webapp/app/exam/overview/events/overlay/exam-live-events-overlay.component.ts index ae8cfa434f31..8940ce0cc7a8 100644 --- a/src/main/webapp/app/exam/overview/events/overlay/exam-live-events-overlay.component.ts +++ b/src/main/webapp/app/exam/overview/events/overlay/exam-live-events-overlay.component.ts @@ -33,7 +33,7 @@ export class ExamLiveEventsOverlayComponent implements OnInit, OnDestroy { eventsToDisplay?: ExamLiveEvent[]; events: ExamLiveEvent[] = []; - examStartDate = model(); + examStartDate = model.required(); // Icons faCheck = faCheck; @@ -53,13 +53,10 @@ export class ExamLiveEventsOverlayComponent implements OnInit, OnDestroy { } }); - const examStartDate = this.examStartDate(); - if (examStartDate) { - this.newLiveEventsSubscription = this.liveEventsService.observeNewEventsAsUser(USER_DISPLAY_RELEVANT_EVENTS, examStartDate).subscribe((event: ExamLiveEvent) => { - this.unacknowledgedEvents.unshift(event); - this.updateEventsToDisplay(); - }); - } + this.newLiveEventsSubscription = this.liveEventsService.observeNewEventsAsUser(USER_DISPLAY_RELEVANT_EVENTS, this.examStartDate()).subscribe((event: ExamLiveEvent) => { + this.unacknowledgedEvents.unshift(event); + this.updateEventsToDisplay(); + }); } acknowledgeEvent(event: ExamLiveEvent) { From 40bbfdf3b3fe7512b2c987d118bced43b99d47d1 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Sat, 26 Apr 2025 09:43:58 +0200 Subject: [PATCH 47/49] remove unused import --- .../exam-navigation-bar/exam-navigation-bar.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/webapp/app/exam/overview/exam-navigation-bar/exam-navigation-bar.component.ts b/src/main/webapp/app/exam/overview/exam-navigation-bar/exam-navigation-bar.component.ts index 246c5651e36d..e206f2af3258 100644 --- a/src/main/webapp/app/exam/overview/exam-navigation-bar/exam-navigation-bar.component.ts +++ b/src/main/webapp/app/exam/overview/exam-navigation-bar/exam-navigation-bar.component.ts @@ -17,7 +17,6 @@ import { faBars, faCheck, faEdit } from '@fortawesome/free-solid-svg-icons'; import { ProgrammingSubmission } from 'app/programming/shared/entities/programming-submission.model'; import { FileUploadSubmission } from 'app/fileupload/shared/entities/file-upload-submission.model'; import { TranslateDirective } from 'app/shared/language/translate.directive'; -import { ExamLiveEventsButtonComponent } from '../events/button/exam-live-events-button.component'; import { NgClass } from '@angular/common'; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; @@ -29,7 +28,7 @@ import { SubmissionVersion } from 'app/exam/shared/entities/submission-version.m selector: 'jhi-exam-navigation-bar', templateUrl: './exam-navigation-bar.component.html', styleUrls: ['./exam-navigation-bar.component.scss'], - imports: [TranslateDirective, ExamLiveEventsButtonComponent, NgClass, NgbTooltip, FaIconComponent, ExamTimerComponent, ArtemisTranslatePipe], + imports: [TranslateDirective, NgClass, NgbTooltip, FaIconComponent, ExamTimerComponent, ArtemisTranslatePipe], }) export class ExamNavigationBarComponent implements OnInit, AfterViewInit { private layoutService = inject(LayoutService); From b80c1d2244e2f34e3a5ddfa705d2125506fd253f Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Sat, 26 Apr 2025 09:47:51 +0200 Subject: [PATCH 48/49] move input setup to beforeEach --- ...exam-general-information.component.spec.ts | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/main/webapp/app/exam/overview/general-information/exam-general-information.component.spec.ts b/src/main/webapp/app/exam/overview/general-information/exam-general-information.component.spec.ts index 327883a5a5e4..c4e675814c1d 100644 --- a/src/main/webapp/app/exam/overview/general-information/exam-general-information.component.spec.ts +++ b/src/main/webapp/app/exam/overview/general-information/exam-general-information.component.spec.ts @@ -38,6 +38,10 @@ describe('ExamGeneralInformationComponent', () => { .then(() => { fixture = TestBed.createComponent(ExamGeneralInformationComponent); component = fixture.componentInstance; + TestBed.runInInjectionContext(() => { + component.exam = input(exam); + component.studentExam = input(studentExam); + }); }); }); @@ -47,7 +51,6 @@ describe('ExamGeneralInformationComponent', () => { it('should initialize', () => { TestBed.runInInjectionContext(() => { - component.exam = input(exam); component.studentExam = input({} as StudentExam); }); fixture.detectChanges(); @@ -57,10 +60,6 @@ describe('ExamGeneralInformationComponent', () => { }); it('should return the start date plus the working time as the student exam end date', () => { - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); component.ngOnChanges(); expect(fixture).toBeDefined(); @@ -70,7 +69,6 @@ describe('ExamGeneralInformationComponent', () => { it('should detect if the end date is on another day', () => { exam.endDate = dayjs(exam.startDate).add(2, 'days'); TestBed.runInInjectionContext(() => { - component.exam = input(exam); component.studentExam = input({} as StudentExam); }); fixture.detectChanges(); @@ -81,10 +79,6 @@ describe('ExamGeneralInformationComponent', () => { it('should detect if the working time extends to another day', () => { studentExam.workingTime = 24 * 60 * 60; - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); component.ngOnChanges(); expect(fixture).toBeDefined(); @@ -92,10 +86,6 @@ describe('ExamGeneralInformationComponent', () => { }); it('should return false for exams that only last one day', () => { - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); component.ngOnChanges(); expect(fixture).toBeDefined(); @@ -109,10 +99,6 @@ describe('ExamGeneralInformationComponent', () => { it('should detect an TestExam and set the currentDate correctly', () => { exam.testExam = true; - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); const minimumNowRange = dayjs(); fixture.detectChanges(); component.ngOnChanges(); @@ -124,10 +110,6 @@ describe('ExamGeneralInformationComponent', () => { }); it('should detect an RealExam and not set the currentDate', () => { - TestBed.runInInjectionContext(() => { - component.exam = input(exam); - component.studentExam = input(studentExam); - }); fixture.detectChanges(); component.ngOnChanges(); expect(component.isTestExam).toBeFalse(); From 5ec982c0f6fdb91a73cb08ad231e01c1de158a94 Mon Sep 17 00:00:00 2001 From: Michal Kawka Date: Mon, 28 Apr 2025 11:23:42 +0200 Subject: [PATCH 49/49] remove uncommented code --- .../exam-cover/exam-participation-cover.component.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/webapp/app/exam/overview/exam-cover/exam-participation-cover.component.spec.ts b/src/main/webapp/app/exam/overview/exam-cover/exam-participation-cover.component.spec.ts index 1f414e8f853c..18e6394ccfb2 100644 --- a/src/main/webapp/app/exam/overview/exam-cover/exam-participation-cover.component.spec.ts +++ b/src/main/webapp/app/exam/overview/exam-cover/exam-participation-cover.component.spec.ts @@ -214,14 +214,12 @@ describe('ExamParticipationCoverComponent', () => { }); it('should submit exam', () => { - // component.onExamEnded = new EventEmitter(); const saveStudentExamSpy = jest.spyOn(component.onExamEnded, 'emit'); component.submitExam(); expect(saveStudentExamSpy).toHaveBeenCalledOnce(); }); it('should continue after handing in early', () => { - // component.onExamContinueAfterHandInEarly = new EventEmitter(); const saveStudentExamSpy = jest.spyOn(component.onExamContinueAfterHandInEarly, 'emit'); component.continueAfterHandInEarly(); expect(saveStudentExamSpy).toHaveBeenCalledOnce();