@if (lecture && (isCommunicationEnabled(lecture.course) || isMessagingEnabled(lecture.course))) {
-
+
}
diff --git a/src/main/webapp/app/overview/course-lectures/course-lecture-details.component.ts b/src/main/webapp/app/overview/course-lectures/course-lecture-details.component.ts
index e1ab434077ff..a87074793132 100644
--- a/src/main/webapp/app/overview/course-lectures/course-lecture-details.component.ts
+++ b/src/main/webapp/app/overview/course-lectures/course-lecture-details.component.ts
@@ -9,7 +9,6 @@ import { Attachment } from 'app/entities/attachment.model';
import { LectureService } from 'app/lecture/lecture.service';
import { LectureUnit, LectureUnitType } from 'app/entities/lecture-unit/lectureUnit.model';
import { AttachmentUnit } from 'app/entities/lecture-unit/attachmentUnit.model';
-import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component';
import { onError } from 'app/shared/util/global.utils';
import { finalize, tap } from 'rxjs/operators';
import { AlertService } from 'app/core/util/alert.service';
@@ -39,7 +38,6 @@ export class CourseLectureDetailsComponent extends AbstractScienceComponent impl
lecture?: Lecture;
isDownloadingLink?: string;
lectureUnits: LectureUnit[] = [];
- discussionComponent?: DiscussionSectionComponent;
hasPdfLectureUnit: boolean;
paramsSubscription: Subscription;
@@ -107,10 +105,6 @@ export class CourseLectureDetailsComponent extends AbstractScienceComponent impl
(unit) => unit.attachment?.link?.split('.').pop()!.toLocaleLowerCase() === 'pdf',
).length > 0;
}
- if (this.discussionComponent) {
- // We need to manually update the lecture property of the student questions component
- this.discussionComponent.lecture = this.lecture;
- }
this.endsSameDay = !!this.lecture?.startDate && !!this.lecture.endDate && dayjs(this.lecture.startDate).isSame(this.lecture.endDate, 'day');
},
error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
@@ -160,18 +154,6 @@ export class CourseLectureDetailsComponent extends AbstractScienceComponent impl
this.lectureUnitService.completeLectureUnit(this.lecture!, event);
}
- /**
- * This function gets called if the router outlet gets activated. This is
- * used only for the DiscussionComponent
- * @param instance The component instance
- */
- onChildActivate(instance: DiscussionSectionComponent) {
- this.discussionComponent = instance; // save the reference to the component instance
- if (this.lecture) {
- instance.lecture = this.lecture;
- }
- }
-
ngOnDestroy() {
this.paramsSubscription?.unsubscribe();
this.profileSubscription?.unsubscribe();
diff --git a/src/main/webapp/app/overview/course-lectures/course-lecture-details.module.ts b/src/main/webapp/app/overview/course-lectures/course-lecture-details.module.ts
index 8885f4eba764..315de44054b0 100644
--- a/src/main/webapp/app/overview/course-lectures/course-lecture-details.module.ts
+++ b/src/main/webapp/app/overview/course-lectures/course-lecture-details.module.ts
@@ -13,6 +13,7 @@ import { VideoUnitComponent } from 'app/overview/course-lectures/video-unit/vide
import { TextUnitComponent } from 'app/overview/course-lectures/text-unit/text-unit.component';
import { OnlineUnitComponent } from 'app/overview/course-lectures/online-unit/online-unit.component';
import { AttachmentUnitComponent } from 'app/overview/course-lectures/attachment-unit/attachment-unit.component';
+import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component';
const routes: Routes = [
{
@@ -23,13 +24,6 @@ const routes: Routes = [
pageTitle: 'overview.lectures',
},
canActivate: [UserRouteAccessService],
- children: [
- {
- path: '',
- pathMatch: 'full',
- loadChildren: () => import('../discussion-section/discussion-section.module').then((m) => m.DiscussionSectionModule),
- },
- ],
},
];
@NgModule({
@@ -45,6 +39,7 @@ const routes: Routes = [
TextUnitComponent,
OnlineUnitComponent,
AttachmentUnitComponent,
+ DiscussionSectionComponent,
],
declarations: [CourseLectureDetailsComponent],
exports: [CourseLectureDetailsComponent],
diff --git a/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts b/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts
index fae15df461dd..63d5873cc3b1 100644
--- a/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts
+++ b/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts
@@ -1,4 +1,4 @@
-import { AfterViewInit, Component, ElementRef, Input, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
+import { AfterViewInit, Component, ElementRef, OnDestroy, QueryList, ViewChild, ViewChildren, effect, input } from '@angular/core';
import interact from 'interactjs';
import { Exercise } from 'app/entities/exercise.model';
import { Lecture } from 'app/entities/lecture.model';
@@ -14,17 +14,22 @@ import { CourseDiscussionDirective } from 'app/shared/metis/course-discussion.di
import { FormBuilder } from '@angular/forms';
import { Channel, ChannelDTO } from 'app/entities/metis/conversation/channel.model';
import { ChannelService } from 'app/shared/metis/conversations/channel.service';
-import { MetisConversationService } from 'app/shared/metis/metis-conversation.service';
+import { ArtemisSharedModule } from 'app/shared/shared.module';
+import { ArtemisPlagiarismCasesSharedModule } from 'app/course/plagiarism-cases/shared/plagiarism-cases-shared.module';
+import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
+import { InfiniteScrollModule } from 'ngx-infinite-scroll';
@Component({
selector: 'jhi-discussion-section',
templateUrl: './discussion-section.component.html',
styleUrls: ['./discussion-section.component.scss'],
+ imports: [FontAwesomeModule, ArtemisSharedModule, ArtemisPlagiarismCasesSharedModule, InfiniteScrollModule],
+ standalone: true,
providers: [MetisService],
})
-export class DiscussionSectionComponent extends CourseDiscussionDirective implements OnInit, AfterViewInit, OnDestroy {
- @Input() exercise?: Exercise;
- @Input() lecture?: Lecture;
+export class DiscussionSectionComponent extends CourseDiscussionDirective implements AfterViewInit, OnDestroy {
+ exercise = input();
+ lecture = input();
@ViewChild(PostCreateEditModalComponent) postCreateEditModal?: PostCreateEditModalComponent;
@ViewChildren('postingThread') messages: QueryList;
@@ -60,22 +65,18 @@ export class DiscussionSectionComponent extends CourseDiscussionDirective implem
private activatedRoute: ActivatedRoute,
private router: Router,
private formBuilder: FormBuilder,
- private metisConversationService: MetisConversationService,
) {
super(metisService);
+ effect(() => this.loadData(this.exercise(), this.lecture()));
}
- /**
- * on initialization: initializes the metis service, fetches the posts for the exercise or lecture the discussion section is placed at,
- * creates the subscription to posts to stay updated on any changes of posts in this course
- */
- ngOnInit(): void {
+ loadData(exercise?: Exercise, lecture?: Lecture): void {
this.paramSubscription = combineLatest({
params: this.activatedRoute.params,
queryParams: this.activatedRoute.queryParams,
}).subscribe((routeParams: { params: Params; queryParams: Params }) => {
this.currentPostId = +routeParams.queryParams.postId;
- this.course = this.exercise?.course ?? this.lecture?.course;
+ this.course = exercise?.course ?? lecture?.course;
this.metisService.setCourse(this.course);
this.metisService.setPageType(this.PAGE_TYPE);
if (routeParams.params.courseId) {
@@ -146,14 +147,14 @@ export class DiscussionSectionComponent extends CourseDiscussionDirective implem
// Currently, an additional REST call is made to retrieve the channel associated with the lecture/exercise
// TODO: Add the channel to the response for loading the lecture/exercise
- if (this.lecture?.id) {
+ if (this.lecture()) {
this.channelService
- .getChannelOfLecture(courseId, this.lecture.id)
+ .getChannelOfLecture(courseId, this.lecture()!.id!)
.pipe(map((res: HttpResponse) => res.body))
.subscribe(getChannel());
- } else if (this.exercise?.id) {
+ } else if (this.exercise()) {
this.channelService
- .getChannelOfExercise(courseId, this.exercise.id)
+ .getChannelOfExercise(courseId, this.exercise()!.id!)
.pipe(map((res: HttpResponse) => res.body))
.subscribe(getChannel());
}
@@ -271,8 +272,8 @@ export class DiscussionSectionComponent extends CourseDiscussionDirective implem
resetFormGroup(): void {
this.formGroup = this.formBuilder.group({
conversationId: this.channel?.id,
- exerciseId: this.exercise?.id,
- lectureId: this.lecture?.id,
+ exerciseId: this.exercise()?.id,
+ lectureId: this.lecture()?.id,
filterToUnresolved: false,
filterToOwn: false,
filterToAnsweredOrReacted: false,
diff --git a/src/main/webapp/app/overview/discussion-section/discussion-section.module.ts b/src/main/webapp/app/overview/discussion-section/discussion-section.module.ts
deleted file mode 100644
index 125a60831748..000000000000
--- a/src/main/webapp/app/overview/discussion-section/discussion-section.module.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { NgModule } from '@angular/core';
-import { ArtemisSharedModule } from 'app/shared/shared.module';
-import { ArtemisSidePanelModule } from 'app/shared/side-panel/side-panel.module';
-import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component';
-import { RouterModule, Routes } from '@angular/router';
-import { MetisModule } from 'app/shared/metis/metis.module';
-import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module';
-import { InfiniteScrollModule } from 'ngx-infinite-scroll';
-
-const routes: Routes = [
- {
- path: '',
- pathMatch: 'full',
- component: DiscussionSectionComponent,
- },
-];
-
-@NgModule({
- imports: [RouterModule.forChild(routes), MetisModule, ArtemisSharedModule, ArtemisSidePanelModule, ArtemisSharedComponentModule, InfiniteScrollModule],
- declarations: [DiscussionSectionComponent],
- exports: [DiscussionSectionComponent],
-})
-export class DiscussionSectionModule {}
diff --git a/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.html b/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.html
index 0d8f889d1ed5..238401e7d4e3 100644
--- a/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.html
+++ b/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.html
@@ -255,7 +255,7 @@
@if (exercise.course && (isCommunicationEnabled(exercise.course) || isMessagingEnabled(exercise.course))) {
-
+
}
diff --git a/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts b/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts
index d0b7264723b6..fc623b457d5d 100644
--- a/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts
+++ b/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts
@@ -23,7 +23,6 @@ import { TeamAssignmentPayload } from 'app/entities/team.model';
import { TeamService } from 'app/exercises/shared/team/team.service';
import { QuizExercise, QuizStatus } from 'app/entities/quiz/quiz-exercise.model';
import { QuizExerciseService } from 'app/exercises/quiz/manage/quiz-exercise.service';
-import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component';
import { ExerciseCategory } from 'app/entities/exercise-category.model';
import { getFirstResultWithComplaintFromResults } from 'app/entities/submission.model';
import { ComplaintService } from 'app/complaints/complaint.service';
@@ -86,7 +85,6 @@ export class CourseExerciseDetailsComponent extends AbstractScienceComponent imp
isAfterAssessmentDueDate: boolean;
allowComplaintsForAutomaticAssessments: boolean;
public gradingCriteria: GradingCriterion[];
- private discussionComponent?: DiscussionSectionComponent;
baseResource: string;
isExamExercise: boolean;
submissionPolicy?: SubmissionPolicy;
@@ -224,10 +222,6 @@ export class CourseExerciseDetailsComponent extends AbstractScienceComponent imp
this.subscribeForNewResults();
this.subscribeToTeamAssignmentUpdates();
- if (this.discussionComponent && this.exercise) {
- // We need to manually update the exercise property of the posts component
- this.discussionComponent.exercise = this.exercise;
- }
this.baseResource = `/course-management/${this.courseId}/${this.exercise.type}-exercises/${this.exercise.id}/`;
}
@@ -416,18 +410,6 @@ export class CourseExerciseDetailsComponent extends AbstractScienceComponent imp
return undefined;
}
- /**
- * This function gets called if the router outlet gets activated. This is
- * used only for the DiscussionComponent
- * @param instance The component instance
- */
- onChildActivate(instance: DiscussionSectionComponent) {
- this.discussionComponent = instance; // save the reference to the component instance
- if (this.exercise) {
- instance.exercise = this.exercise;
- }
- }
-
private onError(error: string) {
this.alertService.error(error);
}
diff --git a/src/main/webapp/app/overview/exercise-details/course-exercise-details.module.ts b/src/main/webapp/app/overview/exercise-details/course-exercise-details.module.ts
index 0b264b8006a0..63f0858ea8b8 100644
--- a/src/main/webapp/app/overview/exercise-details/course-exercise-details.module.ts
+++ b/src/main/webapp/app/overview/exercise-details/course-exercise-details.module.ts
@@ -30,6 +30,7 @@ import { ProblemStatementComponent } from 'app/overview/exercise-details/problem
import { ArtemisFeedbackModule } from 'app/exercises/shared/feedback/feedback.module';
import { ArtemisExerciseInfoModule } from 'app/exercises/shared/exercise-info/exercise-info.module';
import { IrisModule } from 'app/iris/iris.module';
+import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component';
const routes: Routes = [
{
@@ -41,13 +42,6 @@ const routes: Routes = [
},
pathMatch: 'full',
canActivate: [UserRouteAccessService],
- children: [
- {
- path: '',
- pathMatch: 'full',
- loadChildren: () => import('../discussion-section/discussion-section.module').then((m) => m.DiscussionSectionModule),
- },
- ],
},
];
@@ -76,6 +70,7 @@ const routes: Routes = [
ArtemisFeedbackModule,
ArtemisExerciseInfoModule,
IrisModule,
+ DiscussionSectionComponent,
],
declarations: [CourseExerciseDetailsComponent, OrionCourseExerciseDetailsComponent, LtiInitializerComponent, LtiInitializerModalComponent, ProblemStatementComponent],
exports: [CourseExerciseDetailsComponent, OrionCourseExerciseDetailsComponent, ProblemStatementComponent],
diff --git a/src/test/javascript/spec/component/learning-paths/components/learning-path-lecture-unit.component.spec.ts b/src/test/javascript/spec/component/learning-paths/components/learning-path-lecture-unit.component.spec.ts
index a77d15c0709f..03905c6938a7 100644
--- a/src/test/javascript/spec/component/learning-paths/components/learning-path-lecture-unit.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/components/learning-path-lecture-unit.component.spec.ts
@@ -17,20 +17,33 @@ import { AlertService } from 'app/core/util/alert.service';
import { MockAlertService } from '../../../helpers/mocks/service/mock-alert.service';
import { LectureUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/lectureUnit.service';
import { of } from 'rxjs';
+import { CourseInformationSharingConfiguration } from 'app/entities/course.model';
+import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component';
+import { MockComponent } from 'ng-mocks';
+import { LearningPathNavigationService } from 'app/course/learning-paths/services/learning-path-navigation.service';
+import { Lecture } from 'app/entities/lecture.model';
+import { LectureUnitCompletionEvent } from 'app/overview/course-lectures/course-lecture-details.component';
describe('LearningPathLectureUnitComponent', () => {
let component: LearningPathLectureUnitComponent;
let fixture: ComponentFixture;
+ let learningPathNavigationService: LearningPathNavigationService;
let lectureUnitService: LectureUnitService;
let getLectureUnitByIdSpy: jest.SpyInstance;
+ let setLearningObjectCompletionSpy: jest.SpyInstance;
const learningPathId = 1;
const lectureUnit: VideoUnit = {
id: 1,
description: 'Example video unit',
name: 'Example video',
- lecture: { id: 2 },
+ lecture: {
+ id: 2,
+ course: {
+ courseInformationSharingConfiguration: CourseInformationSharingConfiguration.COMMUNICATION_AND_MESSAGING,
+ },
+ },
completed: false,
visibleToStudents: true,
source: 'https://www.youtube.com/embed/8iU8LPEa4o0',
@@ -38,7 +51,7 @@ describe('LearningPathLectureUnitComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [LearningPathLectureUnitComponent],
+ imports: [LearningPathLectureUnitComponent, MockComponent(DiscussionSectionComponent)],
providers: [
provideHttpClient(),
provideHttpClientTesting(),
@@ -70,6 +83,8 @@ describe('LearningPathLectureUnitComponent', () => {
lectureUnitService = TestBed.inject(LectureUnitService);
getLectureUnitByIdSpy = jest.spyOn(lectureUnitService, 'getLectureUnitById').mockReturnValue(of(lectureUnit));
lectureUnitService = TestBed.inject(LectureUnitService);
+ learningPathNavigationService = TestBed.inject(LearningPathNavigationService);
+ setLearningObjectCompletionSpy = jest.spyOn(learningPathNavigationService, 'setCurrentLearningObjectCompletion').mockReturnValue();
fixture = TestBed.createComponent(LearningPathLectureUnitComponent);
component = fixture.componentInstance;
@@ -83,6 +98,7 @@ describe('LearningPathLectureUnitComponent', () => {
it('should initialize', () => {
expect(component).toBeTruthy();
expect(component.lectureUnitId()).toBe(learningPathId);
+ expect(component.isCommunicationEnabled()).toBeFalse();
});
it('should get lecture unit', async () => {
@@ -101,6 +117,38 @@ describe('LearningPathLectureUnitComponent', () => {
expect(component.lectureUnit()).toEqual(lectureUnit);
});
+ it('should not set current learning object on error', async () => {
+ const completeLectureUnitSpy = jest.spyOn(lectureUnitService, 'completeLectureUnit').mockImplementationOnce(() => {});
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ component.setLearningObjectCompletion({ completed: true, lectureUnit: lectureUnit });
+
+ expect(completeLectureUnitSpy).toHaveBeenCalledExactlyOnceWith(lectureUnit.lecture, {
+ completed: true,
+ lectureUnit: lectureUnit,
+ });
+ expect(setLearningObjectCompletionSpy).not.toHaveBeenCalled();
+ });
+
+ it('should set current learning object completion', async () => {
+ const completeLectureUnitSpy = jest
+ .spyOn(lectureUnitService, 'completeLectureUnit')
+ .mockImplementationOnce((lecture: Lecture, completionEvent: LectureUnitCompletionEvent) => (completionEvent.lectureUnit.completed = completionEvent.completed));
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ component.setLearningObjectCompletion({ completed: true, lectureUnit: lectureUnit });
+
+ expect(completeLectureUnitSpy).toHaveBeenCalledExactlyOnceWith(lectureUnit.lecture, {
+ completed: true,
+ lectureUnit: lectureUnit,
+ });
+ expect(setLearningObjectCompletionSpy).toHaveBeenCalledExactlyOnceWith(true);
+ });
+
it('should set loading state correctly', async () => {
const setIsLoadingSpy = jest.spyOn(component.isLoading, 'set');
fixture.detectChanges();
@@ -110,4 +158,27 @@ describe('LearningPathLectureUnitComponent', () => {
expect(setIsLoadingSpy).toHaveBeenCalledWith(true);
expect(setIsLoadingSpy).toHaveBeenCalledWith(false);
});
+
+ it('should show discussion section when communication is enabled', async () => {
+ fixture.detectChanges();
+ await fixture.whenStable();
+ fixture.detectChanges();
+
+ const discussionSection = fixture.nativeElement.querySelector('jhi-discussion-section');
+ expect(discussionSection).toBeTruthy();
+ });
+
+ it('should not show discussion section when communication is disabled', async () => {
+ const lecture = {
+ ...lectureUnit.lecture,
+ course: { courseInformationSharingConfiguration: CourseInformationSharingConfiguration.DISABLED },
+ };
+ getLectureUnitByIdSpy.mockReturnValue(of({ ...lectureUnit, lecture }));
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const discussionSection = fixture.nativeElement.querySelector('jhi-discussion-section');
+ expect(discussionSection).toBeFalsy();
+ });
});
diff --git a/src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts b/src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts
index 16fa583ba3c9..ad24cc2dd512 100644
--- a/src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts
@@ -10,7 +10,6 @@ import { LectureUnitService } from 'app/lecture/lecture-unit/lecture-unit-manage
import { TextUnit } from 'app/entities/lecture-unit/textUnit.model';
import { OnlineUnit } from 'app/entities/lecture-unit/onlineUnit.model';
import { Course, CourseInformationSharingConfiguration } from 'app/entities/course.model';
-import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component';
import { AttachmentUnitComponent } from 'app/overview/course-lectures/attachment-unit/attachment-unit.component';
import { VideoUnitComponent } from 'app/overview/course-lectures/video-unit/video-unit.component';
import { OnlineUnitComponent } from 'app/overview/course-lectures/online-unit/online-unit.component';
@@ -105,17 +104,4 @@ describe('LearningPathLectureUnitViewComponent', () => {
expect(setCompletionStub).toHaveBeenCalledOnce();
expect(setCompletionStub).toHaveBeenCalledWith(attachment.id, lecture.id, event.completed);
});
-
- it('should set properties of child on activate', () => {
- const attachment = new AttachmentUnit();
- attachment.id = 3;
- lecture.lectureUnits = [attachment];
- comp.lecture = lecture;
- comp.lectureUnit = attachment;
- lecture.course = new Course();
- fixture.detectChanges();
- const instance = { lecture: undefined } as DiscussionSectionComponent;
- comp.onChildActivate(instance);
- expect(instance.lecture).toEqual(lecture);
- });
});
diff --git a/src/test/javascript/spec/component/overview/course-lectures/course-lecture-details.component.spec.ts b/src/test/javascript/spec/component/overview/course-lectures/course-lecture-details.component.spec.ts
index f5eecf4d351c..101871555716 100644
--- a/src/test/javascript/spec/component/overview/course-lectures/course-lecture-details.component.spec.ts
+++ b/src/test/javascript/spec/component/overview/course-lectures/course-lecture-details.component.spec.ts
@@ -2,14 +2,13 @@ import { DebugElement } from '@angular/core';
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
-import { RouterTestingModule } from '@angular/router/testing';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { TranslateService } from '@ngx-translate/core';
import { MockComponent, MockDirective, MockPipe, MockProvider } from 'ng-mocks';
import dayjs from 'dayjs/esm';
import { AlertService } from 'app/core/util/alert.service';
import { BehaviorSubject, of } from 'rxjs';
-import { CourseLectureDetailsComponent } from '../../../../../../main/webapp/app/overview/course-lectures/course-lecture-details.component';
+import { CourseLectureDetailsComponent } from 'app/overview/course-lectures/course-lecture-details.component';
import { AttachmentUnitComponent } from 'app/overview/course-lectures/attachment-unit/attachment-unit.component';
import { ExerciseUnitComponent } from 'app/overview/course-lectures/exercise-unit/exercise-unit.component';
import { TextUnitComponent } from 'app/overview/course-lectures/text-unit/text-unit.component';
@@ -21,14 +20,14 @@ import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
import { ArtemisTimeAgoPipe } from 'app/shared/pipes/artemis-time-ago.pipe';
import { SidePanelComponent } from 'app/shared/side-panel/side-panel.component';
import { Lecture } from 'app/entities/lecture.model';
-import { Course } from 'app/entities/course.model';
+import { Course, CourseInformationSharingConfiguration } from 'app/entities/course.model';
import { AttachmentUnit } from 'app/entities/lecture-unit/attachmentUnit.model';
import { Attachment, AttachmentType } from 'app/entities/attachment.model';
import { TextUnit } from 'app/entities/lecture-unit/textUnit.model';
import { FileService } from 'app/shared/http/file.service';
import { LectureService } from 'app/lecture/lecture.service';
import { MockTranslateService } from '../../../helpers/mocks/service/mock-translate.service';
-import { HttpHeaders, HttpResponse } from '@angular/common/http';
+import { HttpHeaders, HttpResponse, provideHttpClient } from '@angular/common/http';
import { HtmlForMarkdownPipe } from 'app/shared/pipes/html-for-markdown.pipe';
import { SubmissionResultStatusComponent } from 'app/overview/submission-result-status.component';
import { ExerciseDetailsStudentActionsComponent } from 'app/overview/exercise-details/exercise-details-student-actions.component';
@@ -39,31 +38,36 @@ import { CourseExerciseRowComponent } from 'app/overview/course-exercises/course
import { MockFileService } from '../../../helpers/mocks/service/mock-file.service';
import { TranslateDirective } from 'app/shared/language/translate.directive';
import { LectureUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/lectureUnit.service';
-import { NgbCollapse, NgbPopover, NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
import { ScienceService } from 'app/shared/science/science.service';
import * as DownloadUtils from 'app/shared/util/download.util';
-import { ProfileService } from '../../../../../../main/webapp/app/shared/layouts/profiles/profile.service';
-import { ProfileInfo } from '../../../../../../main/webapp/app/shared/layouts/profiles/profile-info.model';
+import { ProfileService } from 'app/shared/layouts/profiles/profile.service';
+import { ProfileInfo } from 'app/shared/layouts/profiles/profile-info.model';
import { MockProfileService } from '../../../helpers/mocks/service/mock-profile.service';
import { OnlineUnitComponent } from 'app/overview/course-lectures/online-unit/online-unit.component';
+import { provideHttpClientTesting } from '@angular/common/http/testing';
+import { NgbCollapse, NgbPopover, NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
+import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component';
describe('CourseLectureDetailsComponent', () => {
let fixture: ComponentFixture;
let courseLecturesDetailsComponent: CourseLectureDetailsComponent;
let lecture: Lecture;
+ let course: Course;
let lectureUnit1: AttachmentUnit;
let lectureUnit2: AttachmentUnit;
let lectureUnit3: TextUnit;
let debugElement: DebugElement;
let profileService: ProfileService;
+ let lectureService: LectureService;
let getProfileInfoMock: jest.SpyInstance;
- beforeEach(() => {
+ beforeEach(async () => {
const releaseDate = dayjs('18-03-2020', 'DD-MM-YYYY');
- const course = new Course();
+ course = new Course();
course.id = 456;
+ course.courseInformationSharingConfiguration = CourseInformationSharingConfiguration.COMMUNICATION_AND_MESSAGING;
lecture = new Lecture();
lecture.id = 1;
@@ -88,8 +92,8 @@ describe('CourseLectureDetailsComponent', () => {
headers = headers.set('Content-Type', 'application/json; charset=utf-8');
const response = of(new HttpResponse({ body: lecture, headers, status: 200 }));
- TestBed.configureTestingModule({
- imports: [RouterTestingModule, MockDirective(NgbTooltip), MockDirective(NgbCollapse), MockDirective(NgbPopover)],
+ await TestBed.configureTestingModule({
+ imports: [MockDirective(NgbTooltip), MockDirective(NgbCollapse), MockDirective(NgbPopover)],
declarations: [
CourseLectureDetailsComponent,
MockComponent(AttachmentUnitComponent),
@@ -112,8 +116,11 @@ describe('CourseLectureDetailsComponent', () => {
MockComponent(FaIconComponent),
MockDirective(TranslateDirective),
MockComponent(SubmissionResultStatusComponent),
+ MockComponent(DiscussionSectionComponent),
],
providers: [
+ provideHttpClient(),
+ provideHttpClientTesting(),
MockProvider(LectureService, {
find: () => {
return response;
@@ -136,20 +143,22 @@ describe('CourseLectureDetailsComponent', () => {
MockProvider(Router),
MockProvider(ScienceService),
],
- })
- .compileComponents()
- .then(() => {
- fixture = TestBed.createComponent(CourseLectureDetailsComponent);
- courseLecturesDetailsComponent = fixture.componentInstance;
- debugElement = fixture.debugElement;
-
- // mock profileService
- profileService = fixture.debugElement.injector.get(ProfileService);
- getProfileInfoMock = jest.spyOn(profileService, 'getProfileInfo');
- const profileInfo = { inProduction: false } as ProfileInfo;
- const profileInfoSubject = new BehaviorSubject(profileInfo);
- getProfileInfoMock.mockReturnValue(profileInfoSubject);
- });
+ }).compileComponents();
+
+ lectureService = TestBed.inject(LectureService);
+ jest.spyOn(lectureService, 'findWithDetails').mockReturnValue(response);
+ jest.spyOn(lectureService, 'find').mockReturnValue(response);
+
+ fixture = TestBed.createComponent(CourseLectureDetailsComponent);
+ courseLecturesDetailsComponent = fixture.componentInstance;
+ debugElement = fixture.debugElement;
+
+ // mock profileService
+ profileService = fixture.debugElement.injector.get(ProfileService);
+ getProfileInfoMock = jest.spyOn(profileService, 'getProfileInfo');
+ const profileInfo = { inProduction: false } as ProfileInfo;
+ const profileInfoSubject = new BehaviorSubject(profileInfo);
+ getProfileInfoMock.mockReturnValue(profileInfoSubject);
});
afterEach(() => {
@@ -247,6 +256,27 @@ describe('CourseLectureDetailsComponent', () => {
expect(courseLecturesDetailsComponent.attachmentExtension(attachment)).toBe('N/A');
}));
+ it('should show discussion section when communication is enabled', fakeAsync(() => {
+ fixture.detectChanges();
+
+ const discussionSection = fixture.nativeElement.querySelector('jhi-discussion-section');
+ expect(discussionSection).toBeTruthy();
+ }));
+
+ it('should not show discussion section when communication is disabled', fakeAsync(() => {
+ const lecture = {
+ ...lectureUnit3.lecture,
+ course: { courseInformationSharingConfiguration: CourseInformationSharingConfiguration.DISABLED },
+ };
+ const response = of(new HttpResponse({ body: { ...lecture }, status: 200 }));
+ jest.spyOn(TestBed.inject(LectureService), 'findWithDetails').mockReturnValue(response);
+
+ fixture.detectChanges();
+
+ const discussionSection = fixture.nativeElement.querySelector('jhi-discussion-section');
+ expect(discussionSection).toBeFalsy();
+ }));
+
it('should download file for attachment', fakeAsync(() => {
const fileService = TestBed.inject(FileService);
const downloadFileSpy = jest.spyOn(fileService, 'downloadFile');
diff --git a/src/test/javascript/spec/component/overview/discussion-section/discussion-section.component.spec.ts b/src/test/javascript/spec/component/overview/discussion-section/discussion-section.component.spec.ts
index 55a145f49666..f1f6ec890ecf 100644
--- a/src/test/javascript/spec/component/overview/discussion-section/discussion-section.component.spec.ts
+++ b/src/test/javascript/spec/component/overview/discussion-section/discussion-section.component.spec.ts
@@ -1,8 +1,7 @@
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { of } from 'rxjs';
import { HttpResponse, provideHttpClient } from '@angular/common/http';
-import { MockComponent, MockModule, MockPipe, MockProvider } from 'ng-mocks';
-import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
+import { MockComponent, MockModule, MockProvider } from 'ng-mocks';
import { MetisService } from 'app/shared/metis/metis.service';
import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
import { MockExerciseService } from '../../../helpers/mocks/service/mock-exercise.service';
@@ -13,8 +12,6 @@ import { MockPostService } from '../../../helpers/mocks/service/mock-post.servic
import { AccountService } from 'app/core/auth/account.service';
import { MockAccountService } from '../../../helpers/mocks/service/mock-account.service';
import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component';
-import { PostingThreadComponent } from 'app/shared/metis/posting-thread/posting-thread.component';
-import { PostCreateEditModalComponent } from 'app/shared/metis/posting-create-edit-modal/post-create-edit-modal/post-create-edit-modal.component';
import { MockTranslateService } from '../../../helpers/mocks/service/mock-translate.service';
import { TranslateService } from '@ngx-translate/core';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
@@ -25,7 +22,6 @@ import { LocalStorageService, SessionStorageService } from 'ngx-webstorage';
import { MockLocalStorageService } from '../../../helpers/mocks/service/mock-local-storage.service';
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { getElement, getElements } from '../../../helpers/utils/general.utils';
-import { ButtonComponent } from 'app/shared/components/button.component';
import {
messagesBetweenUser1User2,
metisCourse,
@@ -47,6 +43,7 @@ import { MetisConversationService } from 'app/shared/metis/metis-conversation.se
import { MockMetisConversationService } from '../../../helpers/mocks/service/mock-metis-conversation.service';
import { NotificationService } from 'app/shared/notification/notification.service';
import { MockNotificationService } from '../../../helpers/mocks/service/mock-notification.service';
+import { provideHttpClientTesting } from '@angular/common/http/testing';
@Directive({
// eslint-disable-next-line @angular-eslint/directive-selector
@@ -66,11 +63,12 @@ describe('DiscussionSectionComponent', () => {
let getChannelOfLectureSpy: jest.SpyInstance;
let getChannelOfExerciseSpy: jest.SpyInstance;
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [MockModule(FormsModule), MockModule(ReactiveFormsModule), MockModule(NgbTooltipModule)],
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [MockModule(FormsModule), MockModule(ReactiveFormsModule), MockModule(NgbTooltipModule), DiscussionSectionComponent],
providers: [
provideHttpClient(),
+ provideHttpClientTesting(),
FormBuilder,
MockProvider(SessionStorageService),
MockProvider(ChannelService),
@@ -89,48 +87,36 @@ describe('DiscussionSectionComponent', () => {
useValue: new MockActivatedRoute({ postId: metisPostTechSupport.id, courseId: metisCourse.id }),
},
],
- declarations: [
- DiscussionSectionComponent,
- InfiniteScrollStubDirective,
- MockComponent(PostingThreadComponent),
- MockComponent(PostCreateEditModalComponent),
- MockComponent(FaIconComponent),
- MockComponent(ButtonComponent),
- MockPipe(ArtemisTranslatePipe),
- ],
+ declarations: [InfiniteScrollStubDirective, MockComponent(FaIconComponent)],
})
.overrideComponent(DiscussionSectionComponent, {
set: {
providers: [{ provide: MetisService, useClass: MetisService }],
},
})
- .compileComponents()
- .then(() => {
- fixture = TestBed.createComponent(DiscussionSectionComponent);
- component = fixture.componentInstance;
- metisService = fixture.debugElement.injector.get(MetisService);
- channelService = TestBed.inject(ChannelService);
- getChannelOfLectureSpy = jest.spyOn(channelService, 'getChannelOfLecture').mockReturnValue(
- of(
- new HttpResponse({
- body: metisLectureChannelDTO,
- status: 200,
- }),
- ),
- );
- getChannelOfExerciseSpy = jest.spyOn(channelService, 'getChannelOfExercise').mockReturnValue(
- of(
- new HttpResponse({
- body: metisExerciseChannelDTO,
- status: 200,
- }),
- ),
- );
- metisServiceGetFilteredPostsSpy = jest.spyOn(metisService, 'getFilteredPosts');
- component.lecture = { ...metisLecture, course: metisCourse };
- component.ngOnInit();
- fixture.detectChanges();
- });
+ .compileComponents();
+
+ fixture = TestBed.createComponent(DiscussionSectionComponent);
+ component = fixture.componentInstance;
+ metisService = fixture.debugElement.injector.get(MetisService);
+ channelService = TestBed.inject(ChannelService);
+ getChannelOfLectureSpy = jest.spyOn(channelService, 'getChannelOfLecture').mockReturnValue(
+ of(
+ new HttpResponse({
+ body: metisLectureChannelDTO,
+ status: 200,
+ }),
+ ),
+ );
+ getChannelOfExerciseSpy = jest.spyOn(channelService, 'getChannelOfExercise').mockReturnValue(
+ of(
+ new HttpResponse({
+ body: metisExerciseChannelDTO,
+ status: 200,
+ }),
+ ),
+ );
+ metisServiceGetFilteredPostsSpy = jest.spyOn(metisService, 'getFilteredPosts');
});
afterEach(() => {
@@ -138,8 +124,8 @@ describe('DiscussionSectionComponent', () => {
});
it('should set course and messages for lecture with lecture channel on initialization', fakeAsync(() => {
- component.lecture = { ...metisLecture, course: metisCourse };
- component.ngOnInit();
+ fixture.componentRef.setInput('lecture', { ...metisLecture, course: metisCourse });
+ fixture.detectChanges();
tick();
expect(component.course).toEqual(metisCourse);
expect(component.createdPost).toBeDefined();
@@ -149,9 +135,8 @@ describe('DiscussionSectionComponent', () => {
}));
it('should set course and messages for exercise with exercise channel on initialization', fakeAsync(() => {
- component.lecture = undefined;
- component.exercise = { ...metisExercise, course: metisCourse };
- component.ngOnInit();
+ fixture.componentRef.setInput('exercise', { ...metisExercise, course: metisCourse });
+ fixture.detectChanges();
tick();
expect(component.course).toEqual(metisCourse);
expect(component.createdPost).toBeDefined();
@@ -161,6 +146,8 @@ describe('DiscussionSectionComponent', () => {
}));
it('should reset current post', fakeAsync(() => {
+ fixture.componentRef.setInput('lecture', { ...metisLecture, course: metisCourse });
+ fixture.detectChanges();
component.resetCurrentPost();
tick();
expect(component.currentPost).toBeUndefined();
@@ -168,9 +155,8 @@ describe('DiscussionSectionComponent', () => {
}));
it('should initialize correctly for exercise posts with default settings', fakeAsync(() => {
- component.lecture = undefined;
- component.exercise = { ...metisExercise, course: metisCourse };
- component.ngOnInit();
+ fixture.componentRef.setInput('exercise', { ...metisExercise, course: metisCourse });
+ fixture.detectChanges();
tick();
expect(component.formGroup.get('filterToUnresolved')?.value).toBeFalse();
expect(component.formGroup.get('filterToOwn')?.value).toBeFalse();
@@ -182,32 +168,32 @@ describe('DiscussionSectionComponent', () => {
}));
it('should display one new message button for more then 3 messages in channel', fakeAsync(() => {
- component.exercise = { ...metisExercise, course: metisCourse };
- component.ngOnInit();
+ fixture.componentRef.setInput('exercise', { ...metisExercise, course: metisCourse });
+ fixture.detectChanges();
tick();
fixture.detectChanges();
tick();
component.posts = metisExercisePosts;
fixture.detectChanges();
tick();
- const newPostButtons = getElements(fixture.debugElement, '.btn-primary');
+ const newPostButtons = getElements(fixture.debugElement, '#new-post');
expect(newPostButtons).not.toBeNull();
expect(newPostButtons).toHaveLength(1);
}));
it('should display one new message button', fakeAsync(() => {
- component.exercise = { ...metisExercise, course: metisCourse };
- component.ngOnInit();
+ fixture.componentRef.setInput('exercise', { ...metisExercise, course: metisCourse });
+ fixture.detectChanges();
tick();
fixture.detectChanges();
- const newPostButtons = getElements(fixture.debugElement, '.btn-primary');
+ const newPostButtons = getElements(fixture.debugElement, '#new-post');
expect(newPostButtons).not.toBeNull();
expect(newPostButtons).toHaveLength(1);
}));
it('should show search-bar and filters if not focused to a post', fakeAsync(() => {
- component.exercise = { ...metisExercise, course: metisCourse };
- component.ngOnInit();
+ fixture.componentRef.setInput('exercise', { ...metisExercise, course: metisCourse });
+ fixture.detectChanges();
tick();
fixture.detectChanges();
const searchInput = getElement(fixture.debugElement, 'input[name=searchText]');
@@ -222,8 +208,7 @@ describe('DiscussionSectionComponent', () => {
}));
it('should hide search-bar and filters if focused to a post', fakeAsync(() => {
- component.lecture = undefined;
- component.ngOnInit();
+ fixture.detectChanges();
tick();
fixture.detectChanges();
const searchInput = getElement(fixture.debugElement, 'input[name=searchText]');
@@ -238,9 +223,9 @@ describe('DiscussionSectionComponent', () => {
}));
it('triggering filters should invoke the metis service', fakeAsync(() => {
- component.exercise = { ...metisExercise, course: metisCourse };
+ fixture.componentRef.setInput('exercise', { ...metisExercise, course: metisCourse });
metisServiceGetFilteredPostsSpy.mockReset();
- component.ngOnInit();
+ fixture.detectChanges();
tick();
fixture.detectChanges();
component.formGroup.patchValue({
@@ -268,8 +253,8 @@ describe('DiscussionSectionComponent', () => {
it('loads exercise messages if communication only', fakeAsync(() => {
component.course = { id: 1, courseInformationSharingConfiguration: CourseInformationSharingConfiguration.COMMUNICATION_ONLY } as Course;
- component.exercise = { id: 2 } as Exercise;
- component.lecture = undefined;
+ fixture.componentRef.setInput('exercise', { id: 2 } as Exercise);
+ fixture.detectChanges();
component.setChannel(1);
@@ -283,7 +268,8 @@ describe('DiscussionSectionComponent', () => {
it('loads lecture messages if communication only', fakeAsync(() => {
component.course = { id: 1, courseInformationSharingConfiguration: CourseInformationSharingConfiguration.COMMUNICATION_ONLY } as Course;
- component.lecture = { id: 2 } as Lecture;
+ fixture.componentRef.setInput('lecture', { id: 2 } as Lecture);
+ fixture.detectChanges();
component.setChannel(1);
@@ -297,7 +283,8 @@ describe('DiscussionSectionComponent', () => {
it('collapses sidebar if no channel exists', fakeAsync(() => {
component.course = { id: 1, courseInformationSharingConfiguration: CourseInformationSharingConfiguration.COMMUNICATION_ONLY } as Course;
- component.lecture = { id: 2 } as Lecture;
+ fixture.componentRef.setInput('lecture', { id: 2 } as Lecture);
+ fixture.detectChanges();
getChannelOfLectureSpy = jest.spyOn(channelService, 'getChannelOfLecture').mockReturnValue(
of(
new HttpResponse({
@@ -315,6 +302,9 @@ describe('DiscussionSectionComponent', () => {
}));
it('should react to srcoll up event', fakeAsync(() => {
+ component.course = { id: 1, courseInformationSharingConfiguration: CourseInformationSharingConfiguration.COMMUNICATION_ONLY } as Course;
+ fixture.componentRef.setInput('lecture', { id: 2 } as Lecture);
+ fixture.detectChanges();
const fetchNextPageSpy = jest.spyOn(component, 'fetchNextPage');
const scrolledUp = new CustomEvent('scrolledUp');
@@ -332,6 +322,7 @@ describe('DiscussionSectionComponent', () => {
});
it('should change sort direction', () => {
+ fixture.detectChanges();
component.currentSortDirection = SortDirection.ASCENDING;
component.onChangeSortDir();
expect(component.currentSortDirection).toBe(SortDirection.DESCENDING);
@@ -340,6 +331,9 @@ describe('DiscussionSectionComponent', () => {
});
it('fetches new messages on scroll up if more messages are available', fakeAsync(() => {
+ component.course = { id: 1, courseInformationSharingConfiguration: CourseInformationSharingConfiguration.COMMUNICATION_ONLY } as Course;
+ fixture.componentRef.setInput('lecture', { id: 2 } as Lecture);
+ fixture.detectChanges();
component.posts = [];
const commandMetisToFetchPostsSpy = jest.spyOn(component, 'fetchNextPage');
diff --git a/src/test/javascript/spec/component/overview/exercise-details/course-exercise-details.component.spec.ts b/src/test/javascript/spec/component/overview/exercise-details/course-exercise-details.component.spec.ts
index 5fc8684a61cd..efb66d508837 100644
--- a/src/test/javascript/spec/component/overview/exercise-details/course-exercise-details.component.spec.ts
+++ b/src/test/javascript/spec/component/overview/exercise-details/course-exercise-details.component.spec.ts
@@ -45,7 +45,7 @@ import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { ExtensionPointDirective } from 'app/shared/extension-point/extension-point.directive';
import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe';
import { ComplaintsStudentViewComponent } from 'app/complaints/complaints-for-students/complaints-student-view.component';
-import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { provideHttpClientTesting } from '@angular/common/http/testing';
import { MockRouterLinkDirective } from '../../../helpers/mocks/directive/mock-router-link.directive';
import { LtiInitializerComponent } from 'app/overview/exercise-details/lti-initializer.component';
import { ModelingEditorComponent } from 'app/exercises/modeling/shared/modeling-editor.component';
@@ -71,6 +71,8 @@ import { MockScienceService } from '../../../helpers/mocks/service/mock-science-
import { ScienceEventType } from 'app/shared/science/science.model';
import { PROFILE_IRIS } from 'app/app.constants';
import { ExerciseHintService } from 'app/exercises/shared/exercise-hint/shared/exercise-hint.service';
+import { CourseInformationSharingConfiguration } from 'app/entities/course.model';
+import { provideHttpClient } from '@angular/common/http';
describe('CourseExerciseDetailsComponent', () => {
let comp: CourseExerciseDetailsComponent;
@@ -92,7 +94,15 @@ describe('CourseExerciseDetailsComponent', () => {
let scienceService: ScienceService;
let logEventStub: jest.SpyInstance;
- const exercise = { id: 42, type: ExerciseType.TEXT, studentParticipations: [], course: {} } as unknown as Exercise;
+ const exercise = {
+ id: 42,
+ type: ExerciseType.TEXT,
+ studentParticipations: [],
+ course: {
+ id: 1,
+ courseInformationSharingConfiguration: CourseInformationSharingConfiguration.COMMUNICATION_AND_MESSAGING,
+ },
+ } as unknown as Exercise;
const textExercise = {
id: 24,
@@ -119,11 +129,15 @@ describe('CourseExerciseDetailsComponent', () => {
const parentParams = { courseId: 1 };
const parentRoute = { parent: { parent: { params: of(parentParams) } } } as any as ActivatedRoute;
- const route = { params: of({ exerciseId: exercise.id }), parent: parentRoute, queryParams: of({ welcome: '' }) } as any as ActivatedRoute;
+ const route = {
+ params: of({ exerciseId: exercise.id }),
+ parent: parentRoute,
+ queryParams: of({ welcome: '' }),
+ } as any as ActivatedRoute;
beforeEach(() => {
TestBed.configureTestingModule({
- imports: [HttpClientTestingModule],
+ imports: [MockComponent(DiscussionSectionComponent)],
declarations: [
CourseExerciseDetailsComponent,
MockPipe(ArtemisTranslatePipe),
@@ -152,6 +166,8 @@ describe('CourseExerciseDetailsComponent', () => {
MockComponent(ExerciseInfoComponent),
],
providers: [
+ provideHttpClient(),
+ provideHttpClientTesting(),
{ provide: ActivatedRoute, useValue: route },
{ provide: Router, useClass: MockRouter },
{ provide: ProfileService, useClass: MockProfileService },
@@ -195,7 +211,11 @@ describe('CourseExerciseDetailsComponent', () => {
// mock teamService, needed for team assignment
teamService = fixture.debugElement.injector.get(TeamService);
- const teamAssignmentPayload = { exerciseId: 2, teamId: 2, studentParticipations: [] } as TeamAssignmentPayload;
+ const teamAssignmentPayload = {
+ exerciseId: 2,
+ teamId: 2,
+ studentParticipations: [],
+ } as TeamAssignmentPayload;
jest.spyOn(teamService, 'teamAssignmentUpdates', 'get').mockReturnValue(Promise.resolve(of(teamAssignmentPayload)));
// mock participationService, needed for team assignment
@@ -313,14 +333,6 @@ describe('CourseExerciseDetailsComponent', () => {
expect(comp.exampleSolutionCollapsed).toBeFalse();
});
- it('should store a reference to child component', () => {
- comp.exercise = exercise;
-
- const childComponent = {} as DiscussionSectionComponent;
- comp.onChildActivate(childComponent);
- expect(childComponent.exercise).toEqual(exercise);
- });
-
it('should activate hint', () => {
comp.availableExerciseHints = [{ id: 1 }, { id: 2 }];
comp.activatedExerciseHints = [];
@@ -331,10 +343,47 @@ describe('CourseExerciseDetailsComponent', () => {
expect(comp.activatedExerciseHints).toContain(activatedHint);
});
- it('should handle new programming exercise', () => {
- const childComponent = {} as DiscussionSectionComponent;
- comp.onChildActivate(childComponent);
+ it('should sort results by completion date in ascending order', () => {
+ const result1 = { completionDate: dayjs().subtract(2, 'days') } as Result;
+ const result2 = { completionDate: dayjs().subtract(1, 'day') } as Result;
+ const result3 = { completionDate: dayjs() } as Result;
+
+ const results = [result3, result1, result2];
+ results.sort((a, b) => comp['resultSortFunction'](a, b));
+
+ expect(results).toEqual([result1, result2, result3]);
+ });
+
+ it('should handle results with undefined completion dates', () => {
+ const result1 = { completionDate: dayjs().subtract(2, 'days') } as Result;
+ const result2 = { completionDate: undefined } as Result;
+ const result3 = { completionDate: dayjs() } as Result;
+
+ const results = [result3, result1, result2];
+ results.sort((a, b) => comp['resultSortFunction'](a, b));
+
+ expect(results).toEqual([result1, result3, result2]);
+ });
+
+ it('should handle empty results array', () => {
+ const results: Result[] = [];
+ results.sort((a, b) => comp['resultSortFunction'](a, b));
+
+ expect(results).toEqual([]);
+ });
+
+ it('should handle results with same completion dates', () => {
+ const date = dayjs();
+ const result1 = { completionDate: date } as Result;
+ const result2 = { completionDate: date } as Result;
+
+ const results = [result2, result1];
+ results.sort((a, b) => comp['resultSortFunction'](a, b));
+
+ expect(results).toEqual([result2, result1]);
+ });
+ it('should handle new programming exercise', () => {
const courseId = programmingExercise.course!.id!;
comp.courseId = courseId;
@@ -343,7 +392,6 @@ describe('CourseExerciseDetailsComponent', () => {
expect(comp.baseResource).toBe(`/course-management/${courseId}/${programmingExercise.type}-exercises/${programmingExercise.id}/`);
expect(comp.allowComplaintsForAutomaticAssessments).toBeTrue();
expect(comp.submissionPolicy).toEqual(submissionPolicy);
- expect(childComponent.exercise).toEqual(programmingExercise);
});
it('should handle error when getting latest rated result', fakeAsync(() => {
@@ -432,4 +480,25 @@ describe('CourseExerciseDetailsComponent', () => {
fixture.detectChanges();
expect(logEventStub).toHaveBeenCalledExactlyOnceWith(ScienceEventType.EXERCISE__OPEN, exercise.id);
});
+
+ it('should not show discussion section when communication is disabled', fakeAsync(() => {
+ const newExercise = {
+ ...exercise,
+ course: { id: 1, courseInformationSharingConfiguration: CourseInformationSharingConfiguration.DISABLED },
+ };
+ getExerciseDetailsMock.mockReturnValue(of({ body: newExercise }));
+
+ comp.handleNewExercise({ exercise });
+
+ const discussionSection = fixture.nativeElement.querySelector('jhi-discussion-section');
+ expect(discussionSection).toBeFalsy();
+ }));
+
+ it('should show discussion section when communication is enabled', fakeAsync(() => {
+ fixture.detectChanges();
+ tick(500);
+
+ const discussionSection = fixture.nativeElement.querySelector('jhi-discussion-section');
+ expect(discussionSection).toBeTruthy();
+ }));
});