diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100755 new mode 100644 diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseCodeReviewFeedbackService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseCodeReviewFeedbackService.java index 556f0586a432..5e53a4c02fab 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseCodeReviewFeedbackService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseCodeReviewFeedbackService.java @@ -43,7 +43,8 @@ public class ProgrammingExerciseCodeReviewFeedbackService { private static final Logger log = LoggerFactory.getLogger(ProgrammingExerciseCodeReviewFeedbackService.class); - public static final String NON_GRADED_FEEDBACK_SUGGESTION = "NonGradedFeedbackSuggestion:"; + // feedback.detailText prefix + public static final String PRELIMINARY_FEEDBACK_PREFIX = "NonGradedFeedbackSuggestion:"; private final GroupNotificationService groupNotificationService; @@ -124,7 +125,7 @@ public void generateAutomaticNonGradedFeedback(ProgrammingExerciseStudentPartici automaticResult.setScore(100.0); automaticResult.setSuccessful(null); automaticResult.setCompletionDate(ZonedDateTime.now().plusMinutes(5)); // we do not want to show dates without a completion date, but we want the students to know their - // feedback request is in work + // feedback request is in work automaticResult = this.resultRepository.save(automaticResult); try { @@ -143,17 +144,17 @@ public void generateAutomaticNonGradedFeedback(ProgrammingExerciseStudentPartici String feedbackText; if (Objects.nonNull(individualFeedbackItem.lineStart())) { if (Objects.nonNull(individualFeedbackItem.lineEnd()) && !individualFeedbackItem.lineStart().equals(individualFeedbackItem.lineEnd())) { - feedbackText = String.format(NON_GRADED_FEEDBACK_SUGGESTION + "File %s at lines %d-%d", individualFeedbackItem.filePath(), - individualFeedbackItem.lineStart(), individualFeedbackItem.lineEnd()); + feedbackText = String.format(PRELIMINARY_FEEDBACK_PREFIX + "File %s at lines %d-%d", individualFeedbackItem.filePath(), + individualFeedbackItem.lineStart() + 1, individualFeedbackItem.lineEnd() + 1); } else { - feedbackText = String.format(NON_GRADED_FEEDBACK_SUGGESTION + "File %s at line %d", individualFeedbackItem.filePath(), - individualFeedbackItem.lineStart()); + feedbackText = String.format(PRELIMINARY_FEEDBACK_PREFIX + "File %s at line %d", individualFeedbackItem.filePath(), + individualFeedbackItem.lineStart() + 1); } feedback.setReference(String.format("file:%s_line:%d", individualFeedbackItem.filePath(), individualFeedbackItem.lineStart())); } else { - feedbackText = String.format(NON_GRADED_FEEDBACK_SUGGESTION + "File %s", individualFeedbackItem.filePath()); + feedbackText = String.format(PRELIMINARY_FEEDBACK_PREFIX + "File %s", individualFeedbackItem.filePath()); } feedback.setText(feedbackText); feedback.setDetailText(individualFeedbackItem.description()); diff --git a/src/main/webapp/app/assessment/shared/entities/feedback.model.spec.ts b/src/main/webapp/app/assessment/shared/entities/feedback.model.spec.ts index 29cd0000684f..c5c60da50d11 100644 --- a/src/main/webapp/app/assessment/shared/entities/feedback.model.spec.ts +++ b/src/main/webapp/app/assessment/shared/entities/feedback.model.spec.ts @@ -2,7 +2,7 @@ import { Feedback, FeedbackSuggestionType, FeedbackType, - NON_GRADED_FEEDBACK_SUGGESTION_IDENTIFIER, + PRELIMINARY_FEEDBACK_IDENTIFIER, STATIC_CODE_ANALYSIS_FEEDBACK_IDENTIFIER, SUBMISSION_POLICY_FEEDBACK_IDENTIFIER, buildFeedbackTextForReview, @@ -162,8 +162,37 @@ describe('Feedback', () => { }); it('should correctly detect non graded automatically generated feedback', () => { - const feedback: Feedback = { type: FeedbackType.AUTOMATIC, text: NON_GRADED_FEEDBACK_SUGGESTION_IDENTIFIER + 'content' }; - expect(Feedback.isNonGradedFeedbackSuggestion(feedback)).toBeTrue(); + const feedback: Feedback = { type: FeedbackType.AUTOMATIC, text: PRELIMINARY_FEEDBACK_IDENTIFIER + 'content' }; + expect(Feedback.isPreliminaryFeedback(feedback)).toBeTrue(); + }); + }); + + describe('Feedback with lines information', () => { + it('should include lines information in feedback text if text contains "lines"', () => { + const feedback = new Feedback(); + feedback.detailText = 'This is a detailed feedback.'; + feedback.text = 'NonGradedFeedbackSuggestion:File src/example/file.java at lines 10-20'; + + const expectedText = 'This is a detailed feedback. (lines 10-20)'; + expect(buildFeedbackTextForReview(feedback)).toBe(expectedText); + }); + + it('should not include lines information if text does not contain "lines"', () => { + const feedback = new Feedback(); + feedback.detailText = 'This is a detailed feedback.'; + feedback.text = 'NonGradedFeedbackSuggestion:File src/example/file.java'; + + const expectedText = 'This is a detailed feedback.'; + expect(buildFeedbackTextForReview(feedback)).toBe(expectedText); + }); + + it('should ignore feedback not starting with the expected prefix', () => { + const feedback = new Feedback(); + feedback.detailText = 'This is another type of feedback.'; + feedback.text = 'Some other feedback text unrelated to lines'; + + const expectedText = 'This is another type of feedback.'; + expect(buildFeedbackTextForReview(feedback)).toBe(expectedText); }); }); }); diff --git a/src/main/webapp/app/assessment/shared/entities/feedback.model.ts b/src/main/webapp/app/assessment/shared/entities/feedback.model.ts index a61014a6e61e..b9009a7390c6 100644 --- a/src/main/webapp/app/assessment/shared/entities/feedback.model.ts +++ b/src/main/webapp/app/assessment/shared/entities/feedback.model.ts @@ -33,7 +33,7 @@ export const SUBMISSION_POLICY_FEEDBACK_IDENTIFIER = 'SubPolFeedbackIdentifier:' export const FEEDBACK_SUGGESTION_IDENTIFIER = 'FeedbackSuggestion:'; export const FEEDBACK_SUGGESTION_ACCEPTED_IDENTIFIER = 'FeedbackSuggestion:accepted:'; export const FEEDBACK_SUGGESTION_ADAPTED_IDENTIFIER = 'FeedbackSuggestion:adapted:'; -export const NON_GRADED_FEEDBACK_SUGGESTION_IDENTIFIER = 'NonGradedFeedbackSuggestion:'; +export const PRELIMINARY_FEEDBACK_IDENTIFIER = 'NonGradedFeedbackSuggestion:'; export interface DropInfo { instruction: GradingInstruction; @@ -123,11 +123,11 @@ export class Feedback implements BaseEntity { return that.text.startsWith(FEEDBACK_SUGGESTION_IDENTIFIER); } - public static isNonGradedFeedbackSuggestion(that: Feedback): boolean { + public static isPreliminaryFeedback(that: Feedback): boolean { if (!that.text) { return false; } - return that.text.startsWith(NON_GRADED_FEEDBACK_SUGGESTION_IDENTIFIER); + return that.text.startsWith(PRELIMINARY_FEEDBACK_IDENTIFIER); } /** @@ -297,6 +297,12 @@ export const buildFeedbackTextForReview = (feedback: Feedback, addFeedbackText = } } else if (feedback.detailText) { feedbackText = feedback.detailText; + if (feedback.text && feedback.text.includes('lines')) { + const linesInfo = feedback.text.match(/lines .*/); + if (linesInfo) { + feedbackText += ` (${linesInfo[0]})`; + } + } } else if (addFeedbackText && feedback.text) { feedbackText = feedback.text; } diff --git a/src/main/webapp/app/exercise/feedback/item/programming-feedback-item.service.ts b/src/main/webapp/app/exercise/feedback/item/programming-feedback-item.service.ts index 4be14ec59512..7fbe6f1cb56d 100644 --- a/src/main/webapp/app/exercise/feedback/item/programming-feedback-item.service.ts +++ b/src/main/webapp/app/exercise/feedback/item/programming-feedback-item.service.ts @@ -6,7 +6,7 @@ import { FEEDBACK_SUGGESTION_IDENTIFIER, Feedback, FeedbackType, - NON_GRADED_FEEDBACK_SUGGESTION_IDENTIFIER, + PRELIMINARY_FEEDBACK_IDENTIFIER, STATIC_CODE_ANALYSIS_FEEDBACK_IDENTIFIER, SUBMISSION_POLICY_FEEDBACK_IDENTIFIER, } from 'app/assessment/shared/entities/feedback.model'; @@ -50,9 +50,9 @@ export class ProgrammingFeedbackItemService implements FeedbackItemService { return this.createScaFeedbackItem(feedback, showTestDetails); } else if (Feedback.isFeedbackSuggestion(feedback)) { return this.createFeedbackSuggestionItem(feedback, showTestDetails); - } else if (feedback.type === FeedbackType.AUTOMATIC && !Feedback.isNonGradedFeedbackSuggestion(feedback)) { + } else if (feedback.type === FeedbackType.AUTOMATIC && !Feedback.isPreliminaryFeedback(feedback)) { return this.createAutomaticFeedbackItem(feedback, showTestDetails); - } else if (feedback.type === FeedbackType.AUTOMATIC && Feedback.isNonGradedFeedbackSuggestion(feedback)) { + } else if (feedback.type === FeedbackType.AUTOMATIC && Feedback.isPreliminaryFeedback(feedback)) { return this.createNonGradedFeedbackItem(feedback); } else if ((feedback.type === FeedbackType.MANUAL || feedback.type === FeedbackType.MANUAL_UNREFERENCED) && feedback.gradingInstruction) { return this.createGradingInstructionFeedbackItem(feedback, showTestDetails); @@ -161,7 +161,7 @@ export class ProgrammingFeedbackItemService implements FeedbackItemService { return { type: 'Reviewer', name: this.translateService.instant('artemisApp.result.detail.feedback'), - title: feedback.text?.slice(NON_GRADED_FEEDBACK_SUGGESTION_IDENTIFIER.length), + title: feedback.text?.slice(PRELIMINARY_FEEDBACK_IDENTIFIER.length), text: feedback.detailText, positive: feedback.positive, credits: feedback.credits, diff --git a/src/main/webapp/app/exercise/result/result.service.spec.ts b/src/main/webapp/app/exercise/result/result.service.spec.ts index 8da137d90e9c..841f258140b8 100644 --- a/src/main/webapp/app/exercise/result/result.service.spec.ts +++ b/src/main/webapp/app/exercise/result/result.service.spec.ts @@ -22,7 +22,7 @@ import { AssessmentType } from 'app/assessment/shared/entities/assessment-type.m import { ProgrammingSubmission } from 'app/programming/shared/entities/programming-submission.model'; import { FeedbackType, - NON_GRADED_FEEDBACK_SUGGESTION_IDENTIFIER, + PRELIMINARY_FEEDBACK_IDENTIFIER, STATIC_CODE_ANALYSIS_FEEDBACK_IDENTIFIER, SUBMISSION_POLICY_FEEDBACK_IDENTIFIER, } from 'app/assessment/shared/entities/feedback.model'; @@ -90,14 +90,14 @@ describe('ResultService', () => { score: 80, }; const result6: Result = { - feedbacks: [{ text: NON_GRADED_FEEDBACK_SUGGESTION_IDENTIFIER + 'AI feedback', type: FeedbackType.AUTOMATIC }], + feedbacks: [{ text: PRELIMINARY_FEEDBACK_IDENTIFIER + 'AI feedback', type: FeedbackType.AUTOMATIC }], participation: { type: ParticipationType.PROGRAMMING }, completionDate: dayjs().subtract(5, 'minutes'), assessmentType: AssessmentType.AUTOMATIC_ATHENA, successful: true, }; const result7: Result = { - feedbacks: [{ text: NON_GRADED_FEEDBACK_SUGGESTION_IDENTIFIER + 'AI feedback', type: FeedbackType.AUTOMATIC }], + feedbacks: [{ text: PRELIMINARY_FEEDBACK_IDENTIFIER + 'AI feedback', type: FeedbackType.AUTOMATIC }], participation: { type: ParticipationType.PROGRAMMING }, completionDate: dayjs().subtract(5, 'minutes'), assessmentType: AssessmentType.AUTOMATIC_ATHENA, diff --git a/src/main/webapp/app/exercise/result/updating-result/updating-result.component.ts b/src/main/webapp/app/exercise/result/updating-result/updating-result.component.ts index ede441f3b603..0cccba06c5e8 100644 --- a/src/main/webapp/app/exercise/result/updating-result/updating-result.component.ts +++ b/src/main/webapp/app/exercise/result/updating-result/updating-result.component.ts @@ -42,7 +42,7 @@ export class UpdatingResultComponent implements OnChanges, OnDestroy { @Input() showCompletion = true; @Input() showProgressBar = false; @Input() showProgressBarBorder = false; - @Output() showResult = new EventEmitter(); + @Output() showResult = new EventEmitter(); /** * @property personalParticipation Whether the participation belongs to the user (by being a student) or not (by being an instructor) */ @@ -80,7 +80,7 @@ export class UpdatingResultComponent implements OnChanges, OnDestroy { } if (this.result) { - this.showResult.emit(); + this.showResult.emit(this.result); } } } @@ -125,7 +125,7 @@ export class UpdatingResultComponent implements OnChanges, OnDestroy { } this.onParticipationChange.emit(); if (result) { - this.showResult.emit(); + this.showResult.emit(this.result); } }), ) diff --git a/src/test/javascript/spec/component/exercises/shared/feedback/preliminary-feedback-options.component.spec.ts b/src/main/webapp/app/exercises/shared/preliminary-feedback/preliminary-feedback-options.component.spec.ts similarity index 100% rename from src/test/javascript/spec/component/exercises/shared/feedback/preliminary-feedback-options.component.spec.ts rename to src/main/webapp/app/exercises/shared/preliminary-feedback/preliminary-feedback-options.component.spec.ts diff --git a/src/main/webapp/app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback.component.scss b/src/main/webapp/app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback.component.scss new file mode 100644 index 000000000000..3b0c3f8dc38f --- /dev/null +++ b/src/main/webapp/app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback.component.scss @@ -0,0 +1,14 @@ +.cross { + width: 20px; + height: 20px; + padding: 2px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid #000; + + span { + font-size: 10px; + line-height: 1; + } +} diff --git a/src/main/webapp/app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/code-editor-tutor-assessment-inline-feedback.component.html b/src/main/webapp/app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/code-editor-tutor-assessment-inline-feedback.component.html index b1a6e25bb893..bce36896fa0b 100644 --- a/src/main/webapp/app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/code-editor-tutor-assessment-inline-feedback.component.html +++ b/src/main/webapp/app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/code-editor-tutor-assessment-inline-feedback.component.html @@ -71,7 +71,7 @@ [translateValues]="{ text: this.feedback | feedbackContent | quoted: ' ' }" deleteQuestion="artemisApp.feedback.delete.question" type="submit" - (delete)="deleteFeedback()" + (delete)="deleteFeedback(false)" [dialogError]="dialogError$" class="btn btn-danger btn-sm me-1" > @@ -90,24 +90,31 @@ } } @else { -
+
+ @if (Feedback.isPreliminaryFeedback(feedback)) { + + }
- @if (!Feedback.isNonGradedFeedbackSuggestion(feedback)) { - {{ roundScoreSpecifiedByCourseSettings(feedback.credits, course) + 'P' }} - } + {{ roundScoreSpecifiedByCourseSettings(feedback.credits, course) + 'P' }}
- @if (Feedback.isNonGradedFeedbackSuggestion(feedback)) { + @if (Feedback.isPreliminaryFeedback(feedback)) {

} @else { diff --git a/src/main/webapp/app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/code-editor-tutor-assessment-inline-feedback.component.spec.ts b/src/main/webapp/app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/code-editor-tutor-assessment-inline-feedback.component.spec.ts index f57f4afc7ad2..c9c5576c2a6f 100644 --- a/src/main/webapp/app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/code-editor-tutor-assessment-inline-feedback.component.spec.ts +++ b/src/main/webapp/app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/code-editor-tutor-assessment-inline-feedback.component.spec.ts @@ -2,17 +2,19 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslateService } from '@ngx-translate/core'; import { MockModule, MockProvider } from 'ng-mocks'; import { CodeEditorTutorAssessmentInlineFeedbackComponent } from 'app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/code-editor-tutor-assessment-inline-feedback.component'; -import { Feedback, FeedbackType, NON_GRADED_FEEDBACK_SUGGESTION_IDENTIFIER } from 'app/assessment/shared/entities/feedback.model'; +import { Feedback, FEEDBACK_SUGGESTION_ACCEPTED_IDENTIFIER, FeedbackType, PRELIMINARY_FEEDBACK_IDENTIFIER } from 'app/assessment/shared/entities/feedback.model'; import { GradingInstruction } from 'app/exercise/structured-grading-criterion/grading-instruction.model'; import { StructuredGradingCriterionService } from 'app/exercise/structured-grading-criterion/structured-grading-criterion.service'; import { MockTranslateService } from '../../../../../../../test/javascript/spec/helpers/mocks/service/mock-translate.service'; import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; import { By } from '@angular/platform-browser'; +import { AlertService } from 'app/shared/service/alert.service'; describe('CodeEditorTutorAssessmentInlineFeedbackComponent', () => { let comp: CodeEditorTutorAssessmentInlineFeedbackComponent; let fixture: ComponentFixture; let sgiService: StructuredGradingCriterionService; + let alertService: AlertService; const fileName = 'testFile'; const codeLine = 1; @@ -35,6 +37,7 @@ describe('CodeEditorTutorAssessmentInlineFeedbackComponent', () => { comp.selectedFile = fileName; comp.codeLine = codeLine; sgiService = fixture.debugElement.injector.get(StructuredGradingCriterionService); + alertService = fixture.debugElement.injector.get(AlertService); }); }); @@ -67,7 +70,7 @@ describe('CodeEditorTutorAssessmentInlineFeedbackComponent', () => { it('should delete feedback and emit to parent', () => { const onDeleteFeedbackSpy = jest.spyOn(comp.onDeleteFeedback, 'emit'); - comp.deleteFeedback(); + comp.deleteFeedback(false); expect(onDeleteFeedbackSpy).toHaveBeenCalledOnce(); expect(onDeleteFeedbackSpy).toHaveBeenCalledWith(comp.feedback); @@ -131,34 +134,49 @@ describe('CodeEditorTutorAssessmentInlineFeedbackComponent', () => { expect(textToBeDisplayed).toEqual(expectedTextToBeDisplayed); }); - it('should not display credits and icons for non-graded feedback suggestions', () => { + it('should display credits and icons for positive preliminary feedback', () => { comp.feedback = { type: FeedbackType.AUTOMATIC, - text: NON_GRADED_FEEDBACK_SUGGESTION_IDENTIFIER + 'feedback', + text: PRELIMINARY_FEEDBACK_IDENTIFIER + 'feedback', + credits: 1.5, } as Feedback; fixture.detectChanges(); const badgeElement = fixture.debugElement.query(By.css('.badge')); - expect(badgeElement).toBeNull(); + expect(badgeElement.nativeElement.textContent).toContain('1.5P'); + expect(badgeElement.nativeElement.classList).toContain('bg-success'); }); - it('should display credits and icons for graded feedback', () => { + it('should display credits and icons for negative preliminary feedback', () => { comp.feedback = { - credits: 1, type: FeedbackType.AUTOMATIC, - text: 'feedback', + text: PRELIMINARY_FEEDBACK_IDENTIFIER + 'feedback', + credits: -1.5, + } as Feedback; + fixture.detectChanges(); + + const badgeElement = fixture.debugElement.query(By.css('.badge')); + expect(badgeElement.nativeElement.textContent).toContain('-1.5P'); + expect(badgeElement.nativeElement.classList).toContain('bg-danger'); + }); + + it('should display credits and icons for neutral preliminary feedback', () => { + comp.feedback = { + type: FeedbackType.AUTOMATIC, + text: PRELIMINARY_FEEDBACK_IDENTIFIER + 'feedback', + credits: 0, } as Feedback; fixture.detectChanges(); const badgeElement = fixture.debugElement.query(By.css('.badge')); - expect(badgeElement).not.toBeNull(); - expect(badgeElement.nativeElement.textContent).toContain('1P'); + expect(badgeElement.nativeElement.textContent).toContain('0P'); + expect(badgeElement.nativeElement.classList).toContain('bg-warning'); }); it('should use the correct translation key for non-graded feedback', () => { comp.feedback = { type: FeedbackType.AUTOMATIC, - text: NON_GRADED_FEEDBACK_SUGGESTION_IDENTIFIER + 'feedback', + text: PRELIMINARY_FEEDBACK_IDENTIFIER + 'feedback', } as Feedback; fixture.detectChanges(); @@ -180,4 +198,115 @@ describe('CodeEditorTutorAssessmentInlineFeedbackComponent', () => { const paragraphElement = fixture.debugElement.query(By.css('.col-10 p')).nativeElement; expect(paragraphElement.innerHTML).toContain(comp.buildFeedbackTextForCodeEditor(comp.feedback)); }); + + it('should display the correct translation key for non-graded feedback suggestion', () => { + comp.feedback = { + type: FeedbackType.AUTOMATIC, + text: PRELIMINARY_FEEDBACK_IDENTIFIER + 'feedback', + } as Feedback; + fixture.detectChanges(); + + const translationElement = fixture.debugElement.query(By.css('h6[jhiTranslate="artemisApp.assessment.detail.feedback"]')); + + expect(translationElement).not.toBeNull(); + expect(translationElement.nativeElement.getAttribute('jhiTranslate')).toBe('artemisApp.assessment.detail.feedback'); + }); + + it('should display the correct translation key for graded feedback', () => { + comp.feedback = { + type: FeedbackType.MANUAL, + text: 'Some graded feedback', + } as Feedback; + fixture.detectChanges(); + + const translationElement = fixture.debugElement.query(By.css('h6[jhiTranslate="artemisApp.assessment.detail.tutorComment"]')); + + expect(translationElement).not.toBeNull(); + expect(translationElement.nativeElement.getAttribute('jhiTranslate')).toBe('artemisApp.assessment.detail.tutorComment'); + }); + + describe('Close feedback button', () => { + it('should display the delete button with correct attributes for preliminary feedback', () => { + comp.viewOnly = true; + comp.feedback = { + type: FeedbackType.AUTOMATIC, + text: PRELIMINARY_FEEDBACK_IDENTIFIER + 'feedback', + } as Feedback; + fixture.detectChanges(); + + const buttonElement = fixture.debugElement.query(By.css('button.btn-close.cross')); + + expect(buttonElement.attributes['type']).toBe('button'); + expect(buttonElement.attributes['aria-label']).toBe('Close'); + }); + + it('should not display the delete button for feedback suggestions', () => { + comp.viewOnly = true; + comp.feedback = { + type: FeedbackType.AUTOMATIC_ADAPTED, + text: FEEDBACK_SUGGESTION_ACCEPTED_IDENTIFIER + 'feedback', + } as Feedback; + comp.feedback.text = 'some feedback'; + fixture.detectChanges(); + + const buttonElement = fixture.debugElement.query(By.css('button.btn-close.cross')); + + expect(buttonElement).toBeFalsy(); + }); + + it('should not display the delete button for tutor feedback', () => { + comp.viewOnly = true; + comp.feedback = { + type: FeedbackType.MANUAL, + text: FEEDBACK_SUGGESTION_ACCEPTED_IDENTIFIER + 'some feedback', + } as Feedback; + fixture.detectChanges(); + + const buttonElement = fixture.debugElement.query(By.css('button.btn-close.cross')); + + expect(buttonElement).toBeFalsy(); + }); + + it('should call deleteFeedback method when delete button is clicked', () => { + comp.viewOnly = true; + comp.feedback = { + type: FeedbackType.AUTOMATIC, + text: PRELIMINARY_FEEDBACK_IDENTIFIER + 'feedback', + } as Feedback; + fixture.detectChanges(); + + jest.spyOn(comp, 'deleteFeedback'); + const buttonElement = fixture.debugElement.query(By.css('button.btn-close.cross')); + + buttonElement.nativeElement.click(); + expect(comp.deleteFeedback).toHaveBeenCalled(); + }); + + it('should emit feedback on delete', () => { + jest.spyOn(comp.onDeleteFeedback, 'emit'); + + comp.feedback = { id: 1, text: 'Test feedback' } as Feedback; + comp.deleteFeedback(false); + + expect(comp.onDeleteFeedback.emit).toHaveBeenCalledWith(comp.feedback); + }); + + it('should not display a notification if localStorage key exists', () => { + jest.spyOn(alertService, 'success'); + localStorage.getItem = jest.fn().mockReturnValueOnce(true); + comp.deleteFeedback(true); + + expect(alertService.success).not.toHaveBeenCalled(); + }); + + it('should display a success notification and set localStorage key on first delete', () => { + jest.spyOn(alertService, 'success'); + localStorage.setItem = jest.fn().mockReturnValueOnce(false); + + comp.deleteFeedback(true); + + expect(alertService.success).toHaveBeenCalledWith('artemisApp.editor.showReopenFeedbackHint'); + expect(localStorage.setItem).toHaveBeenCalledWith('jhi-code-editor-tutor-assessment-inline-feedback.showReopenHint', 'true'); + }); + }); }); diff --git a/src/main/webapp/app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/code-editor-tutor-assessment-inline-feedback.component.ts b/src/main/webapp/app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/code-editor-tutor-assessment-inline-feedback.component.ts index 2213dc593d05..faea41a56731 100644 --- a/src/main/webapp/app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/code-editor-tutor-assessment-inline-feedback.component.ts +++ b/src/main/webapp/app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/code-editor-tutor-assessment-inline-feedback.component.ts @@ -6,7 +6,7 @@ import { cloneDeep } from 'lodash-es'; import { StructuredGradingCriterionService } from 'app/exercise/structured-grading-criterion/structured-grading-criterion.service'; import { roundValueSpecifiedByCourseSettings } from 'app/shared/util/utils'; import { Course } from 'app/core/course/shared/entities/course.model'; -import { faBan, faExclamationTriangle, faPencilAlt, faQuestionCircle, faSave, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; +import { faBan, faExclamationTriangle, faPencilAlt, faQuestionCircle, faSave, faTimes, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; import { Subject } from 'rxjs'; import { TranslateDirective } from 'app/shared/language/translate.directive'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; @@ -18,10 +18,12 @@ import { AssessmentCorrectionRoundBadgeComponent } from 'app/assessment/manage/u import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { FeedbackContentPipe } from 'app/shared/pipes/feedback-content.pipe'; import { QuotePipe } from 'app/shared/pipes/quote.pipe'; +import { AlertService } from 'app/shared/service/alert.service'; @Component({ selector: 'jhi-code-editor-tutor-assessment-inline-feedback', templateUrl: './code-editor-tutor-assessment-inline-feedback.component.html', + styleUrls: ['./code-editor-tutor-assessment-inline-feedback.component.scss'], imports: [ FeedbackSuggestionBadgeComponent, TranslateDirective, @@ -43,6 +45,7 @@ export class CodeEditorTutorAssessmentInlineFeedbackComponent { protected readonly faPencilAlt = faPencilAlt; protected readonly faTrashAlt = faTrashAlt; protected readonly faExclamationTriangle = faExclamationTriangle; + protected readonly faTimes = faTimes; protected readonly Feedback = Feedback; protected readonly ButtonSize = ButtonSize; protected readonly MANUAL = FeedbackType.MANUAL; @@ -54,7 +57,10 @@ export class CodeEditorTutorAssessmentInlineFeedbackComponent { // Needed for the outer editor to access the DOM node of this component public elementRef = inject(ElementRef); - @Input() get feedback(): Feedback { + private alertService = inject(AlertService); + + @Input() + get feedback(): Feedback { return this._feedback; } @@ -112,9 +118,18 @@ export class CodeEditorTutorAssessmentInlineFeedbackComponent { } /** - * Deletes feedback after confirmation and emits to parent component + * Deletes feedback with a notification and emits to parent component */ - deleteFeedback() { + deleteFeedback(preliminary: boolean) { + if (preliminary) { + const storageKey = 'jhi-code-editor-tutor-assessment-inline-feedback.showReopenHint'; + + if (!localStorage.getItem(storageKey)) { + this.alertService.success('artemisApp.editor.showReopenFeedbackHint'); + localStorage.setItem(storageKey, 'true'); + } + } + this.onDeleteFeedback.emit(this.feedback); this.dialogErrorSource.next(''); } diff --git a/src/main/webapp/app/programming/manage/code-editor/container/code-editor-container.component.html b/src/main/webapp/app/programming/manage/code-editor/container/code-editor-container.component.html index d5fbc4a0aac8..488f14e7fcc7 100644 --- a/src/main/webapp/app/programming/manage/code-editor/container/code-editor-container.component.html +++ b/src/main/webapp/app/programming/manage/code-editor/container/code-editor-container.component.html @@ -36,18 +36,20 @@

[highlightFileChanges]="highlightFileChanges" [fileBadges]="fileBadges" [allowHiddenFiles]="allowHiddenFiles" + [disableReopenFeedbackButton]="disableReopenFeedbackButton" [(selectedFile)]="selectedFile" [(commitState)]="commitState" (onFileChange)="onFileChange($event)" (onError)="onError($event)" (onToggleCollapse)="onToggleCollapse($event, CollapsableCodeEditorElement.FileBrowser)" + (onReopenFeedback)="onReopenFeedback($event)" /> (); @@ -102,6 +106,8 @@ export class CodeEditorContainerComponent implements OnChanges, ComponentCanDeac onDiscardSuggestion = new EventEmitter(); @Input() course?: Course; + @Input() + latestResult?: Result; /** Work in Progress: temporary properties needed to get first prototype working */ @@ -110,7 +116,7 @@ export class CodeEditorContainerComponent implements OnChanges, ComponentCanDeac /** END WIP */ - // WARNING: Don't initialize variables in the declaration block. The method initializeProperties is responsible for this task. + // WARNING: Don't initialize variables in the declaration block. The method initializeProperties is responsible for this task. selectedFile?: string; unsavedFilesValue: { [fileName: string]: string }; // {[fileName]: fileContent} fileBadges: { [fileName: string]: FileBadge[] }; @@ -127,9 +133,12 @@ export class CodeEditorContainerComponent implements OnChanges, ComponentCanDeac } ngOnChanges(changes: SimpleChanges) { - // Update file badges when feedback suggestions change + // Update file badges when feedback or feedback suggestions change if (changes.feedbackSuggestions) { - this.updateFileBadges(); + this.updateFileBadgesForFeedbackSuggestions(); + } + if (changes.latestResult) { + this.updateFileBadgesForPreliminaryFeedback(); } } @@ -160,9 +169,9 @@ export class CodeEditorContainerComponent implements OnChanges, ComponentCanDeac } /** - * Update the file badges for the code editor (currently only feedback suggestions) + * Update the file badges for the code editor for feedback suggestions */ - updateFileBadges() { + updateFileBadgesForFeedbackSuggestions() { this.fileBadges = {}; // Create badges for feedback suggestions // Get file paths from feedback suggestions: @@ -176,6 +185,27 @@ export class CodeEditorContainerComponent implements OnChanges, ComponentCanDeac } } + /** + * Update the file badges for the code editor for preliminary feedback + */ + updateFileBadgesForPreliminaryFeedback() { + this.fileBadges = {}; + if (this.latestResult?.assessmentType !== AssessmentType.AUTOMATIC_ATHENA) { + return; + } + // Create badges for preliminary feedback + const feedbacks = this.latestResult?.feedbacks ?? []; + // Count only preliminary feedback + const filteredFeedbacks = feedbacks.filter((feedback) => feedback.text?.startsWith(PRELIMINARY_FEEDBACK_IDENTIFIER)); + // Get file paths from feedback: + const filePaths = filteredFeedbacks.map((feedback) => Feedback.getReferenceFilePath(feedback)).filter((filePath) => filePath !== undefined) as string[]; + for (const filePath of filePaths) { + // Count the number of feedback for this file + const feedbackCount = filteredFeedbacks.filter((feedback) => Feedback.getReferenceFilePath(feedback) === filePath).length; + this.fileBadges[filePath] = [new FileBadge(FileBadgeType.PRELIMINARY_FEEDBACK, feedbackCount)]; + } + } + /** * Resets all variables of this class. * When a new variable is added, it needs to be added to this method! @@ -322,4 +352,12 @@ export class CodeEditorContainerComponent implements OnChanges, ComponentCanDeac } return true; } + + /** + * Forces the code editor to refresh the feedback it shows. + * @param fileName The name of the file + */ + onReopenFeedback(fileName: string) { + this.monacoEditor.refreshFeedback(fileName); + } } diff --git a/src/main/webapp/app/programming/manage/code-editor/file-browser/badge/code-editor-file-browser-badge.component.scss b/src/main/webapp/app/programming/manage/code-editor/file-browser/badge/code-editor-file-browser-badge.component.scss index 0fb034e8cf98..d3d85504629c 100644 --- a/src/main/webapp/app/programming/manage/code-editor/file-browser/badge/code-editor-file-browser-badge.component.scss +++ b/src/main/webapp/app/programming/manage/code-editor/file-browser/badge/code-editor-file-browser-badge.component.scss @@ -9,6 +9,7 @@ user-select: none; background-color: var(--code-editor-file-browser-badge-background); color: var(--bs-body-color); + margin-right: 5px; } .badge.on-colored-background { diff --git a/src/main/webapp/app/programming/manage/code-editor/file-browser/badge/code-editor-file-browser-badge.component.spec.ts b/src/main/webapp/app/programming/manage/code-editor/file-browser/badge/code-editor-file-browser-badge.component.spec.ts index 65ad6a4db7e4..4e1ce116d5a6 100644 --- a/src/main/webapp/app/programming/manage/code-editor/file-browser/badge/code-editor-file-browser-badge.component.spec.ts +++ b/src/main/webapp/app/programming/manage/code-editor/file-browser/badge/code-editor-file-browser-badge.component.spec.ts @@ -17,31 +17,50 @@ describe('CodeEditorFileBrowserBadgeComponent', () => { translateService = TestBed.inject(TranslateService); }); - beforeEach(() => { - fixture = TestBed.createComponent(CodeEditorFileBrowserBadgeComponent); - component = fixture.componentInstance; - component.badge = new FileBadge(FileBadgeType.FEEDBACK_SUGGESTION, 3); - fixture.detectChanges(); - }); + describe('Feedback Suggestion Section', () => { + beforeEach(() => { + fixture = TestBed.createComponent(CodeEditorFileBrowserBadgeComponent); + component = fixture.componentInstance; + component.badge = new FileBadge(FileBadgeType.FEEDBACK_SUGGESTION, 3); + fixture.detectChanges(); + }); - it('should correctly display the tooltip for a FEEDBACK_SUGGESTION badge', () => { - jest.spyOn(translateService, 'instant').mockReturnValue('Mocked Tooltip'); - expect(component.tooltip).toBe('Mocked Tooltip'); - }); + it('should correctly display the tooltip for a FEEDBACK_SUGGESTION badge', () => { + jest.spyOn(translateService, 'instant').mockReturnValue('Mocked Tooltip'); + expect(component.tooltip).toBe('Mocked Tooltip'); + }); - it('should return faLightbulb icon for a FEEDBACK_SUGGESTION badge', () => { - expect(component.icon!.iconName).toBe('lightbulb'); - }); + it('should return faLightbulb icon for a FEEDBACK_SUGGESTION badge', () => { + expect(component.icon!.iconName).toBe('lightbulb'); + }); - it('should not have an icon for an unknown badge type', () => { - component.badge = new FileBadge('unknown' as FileBadgeType, 3); - fixture.detectChanges(); - expect(component.icon).toBeUndefined(); + it('should not have an icon for an unknown badge type', () => { + component.badge = new FileBadge('unknown' as FileBadgeType, 3); + fixture.detectChanges(); + expect(component.icon).toBeUndefined(); + }); + + it('should not have a tooltip for an unknown badge type', () => { + component.badge = new FileBadge('unknown' as FileBadgeType, 3); + fixture.detectChanges(); + expect(component.tooltip).toBeUndefined(); + }); }); - it('should not have a tooltip for an unknown badge type', () => { - component.badge = new FileBadge('unknown' as FileBadgeType, 3); - fixture.detectChanges(); - expect(component.tooltip).toBeUndefined(); + describe('Preliminary Feedback Section', () => { + beforeEach(() => { + fixture = TestBed.createComponent(CodeEditorFileBrowserBadgeComponent); + component = fixture.componentInstance; + component.badge = new FileBadge(FileBadgeType.PRELIMINARY_FEEDBACK, 3); + fixture.detectChanges(); + }); + + it('should correctly display the tooltip for a PRELIMINARY_FEEDBACK badge', () => { + expect(component.tooltip).toBe('artemisApp.editor.fileBrowser.fileBadgeTooltips.preliminaryFeedback'); + }); + + it('should return faLightbulb icon for a PRELIMINARY_FEEDBACK badge', () => { + expect(component.icon!.iconName).toBe('lightbulb'); + }); }); }); diff --git a/src/main/webapp/app/programming/manage/code-editor/file-browser/badge/code-editor-file-browser-badge.component.ts b/src/main/webapp/app/programming/manage/code-editor/file-browser/badge/code-editor-file-browser-badge.component.ts index e6d839ae6983..397f7db92a55 100644 --- a/src/main/webapp/app/programming/manage/code-editor/file-browser/badge/code-editor-file-browser-badge.component.ts +++ b/src/main/webapp/app/programming/manage/code-editor/file-browser/badge/code-editor-file-browser-badge.component.ts @@ -23,6 +23,8 @@ export class CodeEditorFileBrowserBadgeComponent { switch (this.badge.type) { case FileBadgeType.FEEDBACK_SUGGESTION: return this.translateService.instant('artemisApp.editor.fileBrowser.fileBadgeTooltips.feedbackSuggestions'); + case FileBadgeType.PRELIMINARY_FEEDBACK: + return this.translateService.instant('artemisApp.editor.fileBrowser.fileBadgeTooltips.preliminaryFeedback'); default: return undefined; } @@ -32,6 +34,8 @@ export class CodeEditorFileBrowserBadgeComponent { switch (this.badge.type) { case FileBadgeType.FEEDBACK_SUGGESTION: return faLightbulb; + case FileBadgeType.PRELIMINARY_FEEDBACK: + return faLightbulb; default: return undefined; } diff --git a/src/main/webapp/app/programming/manage/code-editor/file-browser/badge/code-editor-file-browser-file.component.spec.ts b/src/main/webapp/app/programming/manage/code-editor/file-browser/badge/code-editor-file-browser-file.component.spec.ts new file mode 100644 index 000000000000..ab8cf56a9952 --- /dev/null +++ b/src/main/webapp/app/programming/manage/code-editor/file-browser/badge/code-editor-file-browser-file.component.spec.ts @@ -0,0 +1,70 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { By } from '@angular/platform-browser'; +import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; +import { MockPipe } from 'ng-mocks'; +import { CodeEditorFileBrowserFileComponent } from 'app/programming/manage/code-editor/file-browser/code-editor-file-browser-file.component'; +import { TreeViewItem } from 'app/programming/shared/code-editor/treeview/models/tree-view-item'; + +describe('CodeEditorFileBrowserFileComponent', () => { + let component: CodeEditorFileBrowserFileComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FontAwesomeModule, MockPipe(ArtemisTranslatePipe)], + declarations: [CodeEditorFileBrowserFileComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CodeEditorFileBrowserFileComponent); + component = fixture.componentInstance; + component.disableActions = false; + component.disableReopenFeedbackButton = false; + component.item = { value: 'TestFile', checked: true } as unknown as TreeViewItem; + fixture.detectChanges(); + }); + + describe('Reopen Feedback Button', () => { + it('should render the reopen feedback button with correct id and attributes', () => { + const reopenButton = fixture.debugElement.query(By.css('#file-browser-reopen-feedback')).parent!.nativeElement; + + expect(reopenButton).not.toBeNull(); + expect(reopenButton.classList).toContain('btn'); + expect(reopenButton.classList).toContain('btn-small'); + }); + + it('should render the correct icon for reopen feedback button', () => { + const iconElement = fixture.debugElement.query(By.css('#file-browser-reopen-feedback')).nativeElement; + + expect(iconElement).not.toBeNull(); + expect(component.faEye.iconName).toBe('eye'); + }); + + it('should call reopenFeedback when the reopen feedback button is clicked', () => { + jest.spyOn(component, 'reopenFeedback'); + const reopenButton = fixture.debugElement.query(By.css('#file-browser-reopen-feedback')).parent!.nativeElement; + + reopenButton.click(); + expect(component.reopenFeedback).toHaveBeenCalled(); + }); + + it('should stop propagation of the click event', () => { + const mockEvent = { stopPropagation: jest.fn() } as any; + jest.spyOn(mockEvent, 'stopPropagation'); + component.reopenFeedback(mockEvent); + + expect(mockEvent.stopPropagation).toHaveBeenCalledOnce(); + }); + + it('should emit onReopenFeedbackNode event with correct item', () => { + const mockEvent = { stopPropagation: jest.fn() } as any; + jest.spyOn(component.onReopenFeedbackNode, 'emit'); + + component.reopenFeedback(mockEvent); + + expect(component.onReopenFeedbackNode.emit).toHaveBeenCalledWith(component.item); + }); + }); +}); diff --git a/src/main/webapp/app/programming/manage/code-editor/file-browser/code-editor-file-browser.component.html b/src/main/webapp/app/programming/manage/code-editor/file-browser/code-editor-file-browser.component.html index 485965e4eb4a..6c3eb29b3ec3 100644 --- a/src/main/webapp/app/programming/manage/code-editor/file-browser/code-editor-file-browser.component.html +++ b/src/main/webapp/app/programming/manage/code-editor/file-browser/code-editor-file-browser.component.html @@ -109,11 +109,13 @@

[hasUnsavedChanges]="unsavedFiles && unsavedFiles.includes(item.value)" [hasError]="errorFiles && errorFiles.includes(item.value)" [disableActions]="disableActions || commitState === CommitState.CONFLICT" + [disableReopenFeedbackButton]="disableReopenFeedbackButton" (onSetRenamingNode)="setRenamingFile(item)" (onRenameNode)="onRenameFile($event)" (onNodeSelect)="handleNodeSelected(item)" (onDeleteNode)="openDeleteFileModal(item)" (onClearRenamingNode)="clearRenamingFile()" + (onReopenFeedbackNode)="reopenFeedback(item)" /> } diff --git a/src/main/webapp/app/programming/manage/code-editor/file-browser/code-editor-file-browser.component.spec.ts b/src/main/webapp/app/programming/manage/code-editor/file-browser/code-editor-file-browser.component.spec.ts index e3b27ce27126..a7960b2474a1 100644 --- a/src/main/webapp/app/programming/manage/code-editor/file-browser/code-editor-file-browser.component.spec.ts +++ b/src/main/webapp/app/programming/manage/code-editor/file-browser/code-editor-file-browser.component.spec.ts @@ -929,6 +929,8 @@ describe('CodeEditorFileBrowserComponent', () => { 'folderA/file': [new FileBadge(FileBadgeType.FEEDBACK_SUGGESTION, 1)], 'folderB/file1': [new FileBadge(FileBadgeType.FEEDBACK_SUGGESTION, 1)], 'folderB/file2': [new FileBadge(FileBadgeType.FEEDBACK_SUGGESTION, 2)], + 'folderC/file1': [new FileBadge(FileBadgeType.PRELIMINARY_FEEDBACK, 1)], + 'folderC/file2': [new FileBadge(FileBadgeType.PRELIMINARY_FEEDBACK, 2)], }; beforeEach(() => { @@ -945,9 +947,23 @@ describe('CodeEditorFileBrowserComponent', () => { expect(result).toEqual([new FileBadge(FileBadgeType.FEEDBACK_SUGGESTION, 1)]); }); - it('should aggregate file badges for a collapsed folder', () => { + it('should aggregate file badges for a collapsed folder FEEDBACK_SUGGESTION', () => { const result = comp.getFolderBadges({ value: 'folderB', collapsed: true } as TreeViewItem); expect(result).toEqual([new FileBadge(FileBadgeType.FEEDBACK_SUGGESTION, 3)]); // 1 + 2 }); + + it('should aggregate file badges for a collapsed folder for PRELIMINARY_FEEDBACK', () => { + const result = comp.getFolderBadges({ value: 'folderC', collapsed: true } as TreeViewItem); + expect(result).toEqual([new FileBadge(FileBadgeType.PRELIMINARY_FEEDBACK, 3)]); // 1 + 2 + }); + }); + + it('should emit onReopenFeedback when reopenFeedback is called', () => { + const treeViewItem = { value: 'file1.java' } as TreeViewItem; + const emitSpy = jest.spyOn(comp.onReopenFeedback, 'emit'); + + comp.reopenFeedback(treeViewItem); + + expect(emitSpy).toHaveBeenCalledWith('file1.java'); }); }); diff --git a/src/main/webapp/app/programming/manage/code-editor/file-browser/code-editor-file-browser.component.ts b/src/main/webapp/app/programming/manage/code-editor/file-browser/code-editor-file-browser.component.ts index 73915a6f9b11..f6b8d7b92b2c 100644 --- a/src/main/webapp/app/programming/manage/code-editor/file-browser/code-editor-file-browser.component.ts +++ b/src/main/webapp/app/programming/manage/code-editor/file-browser/code-editor-file-browser.component.ts @@ -105,6 +105,8 @@ export class CodeEditorFileBrowserComponent implements OnInit, OnChanges, AfterV fileBadges: { [path: string]: FileBadge[] } = {}; @Input() allowHiddenFiles = false; + @Input() + disableReopenFeedbackButton: boolean = true; @Output() onToggleCollapse = new EventEmitter(); @@ -116,6 +118,8 @@ export class CodeEditorFileBrowserComponent implements OnInit, OnChanges, AfterV commitStateChange = new EventEmitter(); @Output() onError = new EventEmitter(); + @Output() + onReopenFeedback = new EventEmitter(); isLoadingFiles: boolean; selectedFileValue?: string; @@ -558,6 +562,10 @@ export class CodeEditorFileBrowserComponent implements OnInit, OnChanges, AfterV return this.repositoryFileService.createFolder(folderName); }; + reopenFeedback = (item: TreeViewItem): void => { + this.onReopenFeedback.emit(item.value); + }; + /** * Opens a popup to delete the selected repository file */ diff --git a/src/main/webapp/app/programming/manage/code-editor/file-browser/file/code-editor-file-browser-file.component.html b/src/main/webapp/app/programming/manage/code-editor/file-browser/file/code-editor-file-browser-file.component.html index 45aa1aeef75b..8fabdfae81c2 100644 --- a/src/main/webapp/app/programming/manage/code-editor/file-browser/file/code-editor-file-browser-file.component.html +++ b/src/main/webapp/app/programming/manage/code-editor/file-browser/file/code-editor-file-browser-file.component.html @@ -30,6 +30,11 @@ @if (!disableActions) { + @if (!disableReopenFeedbackButton) { + + } diff --git a/src/main/webapp/app/programming/manage/code-editor/file-browser/file/code-editor-file-browser-file.component.ts b/src/main/webapp/app/programming/manage/code-editor/file-browser/file/code-editor-file-browser-file.component.ts index 469981d26239..420e786a944a 100644 --- a/src/main/webapp/app/programming/manage/code-editor/file-browser/file/code-editor-file-browser-file.component.ts +++ b/src/main/webapp/app/programming/manage/code-editor/file-browser/file/code-editor-file-browser-file.component.ts @@ -1,5 +1,5 @@ import { Component, Input } from '@angular/core'; -import { faEdit, faFile, faTrash } from '@fortawesome/free-solid-svg-icons'; +import { faEdit, faEye, faFile, faTrash } from '@fortawesome/free-solid-svg-icons'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { CodeEditorFileBrowserNodeComponent } from 'app/programming/manage/code-editor/file-browser/node/code-editor-file-browser-node.component'; import { FileBadge } from 'app/programming/shared/code-editor/model/code-editor.model'; @@ -15,6 +15,7 @@ import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; imports: [FaIconComponent, NgClass, CodeEditorFileBrowserBadgeComponent, ArtemisTranslatePipe], }) export class CodeEditorFileBrowserFileComponent extends CodeEditorFileBrowserNodeComponent { + @Input() disableReopenFeedbackButton: boolean = true; @Input() disableActions: boolean; @Input() hasChanges = false; @Input() badges: FileBadge[] = []; @@ -23,4 +24,5 @@ export class CodeEditorFileBrowserFileComponent extends CodeEditorFileBrowserNod faTrash = faTrash; faEdit = faEdit; faFile = faFile; + faEye = faEye; } diff --git a/src/main/webapp/app/programming/manage/code-editor/file-browser/node/code-editor-file-browser-node.component.ts b/src/main/webapp/app/programming/manage/code-editor/file-browser/node/code-editor-file-browser-node.component.ts index 33453a0f7291..304ded307ac5 100644 --- a/src/main/webapp/app/programming/manage/code-editor/file-browser/node/code-editor-file-browser-node.component.ts +++ b/src/main/webapp/app/programming/manage/code-editor/file-browser/node/code-editor-file-browser-node.component.ts @@ -20,6 +20,7 @@ export abstract class CodeEditorFileBrowserNodeComponent implements OnChanges { @Output() onClearRenamingNode = new EventEmitter(); @Output() onRenameNode = new EventEmitter<{ item: TreeViewItem; newFileName: string }>(); @Output() onDeleteNode = new EventEmitter>(); + @Output() onReopenFeedbackNode = new EventEmitter>(); /** * Check if the node is being renamed now, if so, focus the input when the view is rendered. @@ -76,4 +77,13 @@ export abstract class CodeEditorFileBrowserNodeComponent implements OnChanges { event.stopPropagation(); this.onDeleteNode.emit(this.item); } + + /** + * Reopen feedback for this file + * @param event + */ + reopenFeedback(event: any) { + event.stopPropagation(); + this.onReopenFeedbackNode.emit(this.item); + } } diff --git a/src/main/webapp/app/programming/overview/code-editor-student-container/code-editor-student-container.component.html b/src/main/webapp/app/programming/overview/code-editor-student-container/code-editor-student-container.component.html index d8f696cdf420..e1da4c16f92e 100644 --- a/src/main/webapp/app/programming/overview/code-editor-student-container/code-editor-student-container.component.html +++ b/src/main/webapp/app/programming/overview/code-editor-student-container/code-editor-student-container.component.html @@ -29,7 +29,9 @@ [showEditorInstructions]="showEditorInstructions" [isTutorAssessment]="hasTutorAssessment" [readOnlyManualFeedback]="true" + [latestResult]="latestResult" [course]="course" + [disableReopenFeedbackButton]="false" >
{{ exercise?.title }} @@ -63,17 +65,18 @@ } @if (participation) { - + } { expect(getParticipationSubmissionCountSpy).not.toHaveBeenCalled(); }, ); + + it('Should invalidate old feedback suggestions when new feedback arrives', () => {}); }); diff --git a/src/main/webapp/app/programming/overview/code-editor-student-container/code-editor-student-container.component.ts b/src/main/webapp/app/programming/overview/code-editor-student-container/code-editor-student-container.component.ts index 23623c0d70e0..4e0ac1b50be3 100644 --- a/src/main/webapp/app/programming/overview/code-editor-student-container/code-editor-student-container.component.ts +++ b/src/main/webapp/app/programming/overview/code-editor-student-container/code-editor-student-container.component.ts @@ -182,7 +182,11 @@ export class CodeEditorStudentContainerComponent implements OnInit, OnDestroy { return []; } - receivedNewResult() { + receivedParticipationChange() { this.getNumberOfSubmissionsForSubmissionPolicy(); } + + receivedNewResult(result: Result) { + this.latestResult = result; + } } diff --git a/src/main/webapp/app/programming/shared/code-editor/model/code-editor.model.ts b/src/main/webapp/app/programming/shared/code-editor/model/code-editor.model.ts index 2b6e4757bb8c..e23e4d7b0b29 100644 --- a/src/main/webapp/app/programming/shared/code-editor/model/code-editor.model.ts +++ b/src/main/webapp/app/programming/shared/code-editor/model/code-editor.model.ts @@ -124,6 +124,7 @@ export enum GitConflictState { */ export enum FileBadgeType { FEEDBACK_SUGGESTION = 'FEEDBACK_SUGGESTION', + PRELIMINARY_FEEDBACK = 'PRELIMINARY_FEEDBACK', } /** diff --git a/src/main/webapp/app/programming/shared/code-editor/monaco/code-editor-monaco.component.spec.ts b/src/main/webapp/app/programming/shared/code-editor/monaco/code-editor-monaco.component.spec.ts index 1e6e723f3294..34997865e852 100644 --- a/src/main/webapp/app/programming/shared/code-editor/monaco/code-editor-monaco.component.spec.ts +++ b/src/main/webapp/app/programming/shared/code-editor/monaco/code-editor-monaco.component.spec.ts @@ -21,7 +21,7 @@ import { TranslateService } from '@ngx-translate/core'; describe('CodeEditorMonacoComponent', () => { let comp: CodeEditorMonacoComponent; let fixture: ComponentFixture; - let getInlineFeedbackNodeStub: jest.SpyInstance; + let getInlineFeedbackNodeForManualFeedbackStub: jest.SpyInstance; let codeEditorRepositoryFileService: CodeEditorRepositoryFileService; let loadFileFromRepositoryStub: jest.SpyInstance; @@ -44,6 +44,12 @@ describe('CodeEditorMonacoComponent', () => { text: 'comment on line 9', detailText: 'the most detailed text', }, + { + id: 4, + reference: 'file:file2.java_line:9', + text: 'another comment on line 9', + detailText: 'plagiarism statement', + }, ]; beforeEach(async () => { @@ -62,7 +68,7 @@ describe('CodeEditorMonacoComponent', () => { comp = fixture.componentInstance; codeEditorRepositoryFileService = fixture.debugElement.injector.get(CodeEditorRepositoryFileService); loadFileFromRepositoryStub = jest.spyOn(codeEditorRepositoryFileService, 'getFile'); - getInlineFeedbackNodeStub = jest.spyOn(comp, 'getInlineFeedbackNode').mockReturnValue(document.createElement('div')); + getInlineFeedbackNodeForManualFeedbackStub = jest.spyOn(comp, 'getInlineFeedbackNodeForManualFeedback').mockReturnValue(document.createElement('div')); global.ResizeObserver = jest.fn().mockImplementation((callback: ResizeObserverCallback) => { return new MockResizeObserver(callback); }); @@ -311,7 +317,7 @@ describe('CodeEditorMonacoComponent', () => { expect(setAnnotationsStub).toHaveBeenNthCalledWith(3, [buildAnnotations[1]], false); }); - it('should display feedback when viewing a tutor assessment', fakeAsync(() => { + it('should display feedback(without multiple widgets at the same line) when viewing a tutor assessment', fakeAsync(() => { const addLineWidgetStub = jest.spyOn(comp.editor(), 'addLineWidget').mockImplementation(); const selectFileInEditorStub = jest.spyOn(comp, 'selectFileInEditor').mockImplementation(); fixture.componentRef.setInput('isTutorAssessment', true); @@ -326,7 +332,7 @@ describe('CodeEditorMonacoComponent', () => { expect(addLineWidgetStub).toHaveBeenCalledTimes(2); expect(addLineWidgetStub).toHaveBeenNthCalledWith(1, 2, `feedback-1`, document.createElement('div')); expect(addLineWidgetStub).toHaveBeenNthCalledWith(2, 3, `feedback-2`, document.createElement('div')); - expect(getInlineFeedbackNodeStub).toHaveBeenCalledTimes(2); + expect(getInlineFeedbackNodeForManualFeedbackStub).toHaveBeenCalledTimes(2); expect(selectFileInEditorStub).toHaveBeenCalledOnce(); }); })); @@ -337,11 +343,11 @@ describe('CodeEditorMonacoComponent', () => { const feedbackLineZeroBased = feedbackLineOneBased - 1; const addLineWidgetStub = jest.spyOn(comp.editor(), 'addLineWidget').mockImplementation(); const element = document.createElement('div'); - getInlineFeedbackNodeStub.mockReturnValue(undefined); + getInlineFeedbackNodeForManualFeedbackStub.mockReturnValue(undefined); fixture.detectChanges(); // Simulate adding the element comp.addNewFeedback(feedbackLineOneBased); - getInlineFeedbackNodeStub.mockReturnValue(element); + getInlineFeedbackNodeForManualFeedbackStub.mockReturnValue(element); expect(comp.newFeedbackLines()).toEqual([feedbackLineZeroBased]); tick(1); expect(addLineWidgetStub).toHaveBeenCalledExactlyOnceWith(feedbackLineOneBased, `feedback-new-${feedbackLineZeroBased}`, element); @@ -497,4 +503,67 @@ describe('CodeEditorMonacoComponent', () => { await comp.selectFileInEditor(scrolledFile); expect(setScrollTopStub).toHaveBeenCalledExactlyOnceWith(scrollTop); }); + + it('should add only closed feedback for the selected file', () => { + const selectedFile = 'file1.java'; + + const openFeedback = { reference: 'file:file1.java_line:1', text: 'already open feedback' } as Feedback; + const closedFeedbackForFile = { reference: 'file:file1.java_lines:2-3', text: 'closed feedback' } as Feedback; + const closedFeedbackForOtherFile = { reference: 'file:file2.java_line:3', text: 'closed feedback for another file' } as Feedback; + + fixture.componentRef.setInput('feedbacks', [openFeedback, closedFeedbackForFile, closedFeedbackForOtherFile]); + fixture.componentRef.setInput('selectedFile', selectedFile); + const emitSpy = jest.spyOn(comp.onUpdateFeedback, 'emit'); + + fixture.detectChanges(); + + comp.feedbackInternal.set([openFeedback]); + + comp.refreshFeedback(selectedFile); + + expect(comp.feedbackInternal()).toEqual([openFeedback, closedFeedbackForFile]); + + expect(emitSpy).toHaveBeenCalledWith([openFeedback, closedFeedbackForFile]); + }); + + it('should add not change anything if reopening feedback for a non-chosen file', () => { + const selectedFile = 'file1.java'; + + const closedFeedback1 = { reference: 'file:file2.java_line:1', text: 'closed feedback 1' } as Feedback; + const closedFeedback2 = { reference: 'file:file2.java_lines:2-3', text: 'closed feedback 2' } as Feedback; + + fixture.componentRef.setInput('feedbacks', [closedFeedback1, closedFeedback2]); + fixture.componentRef.setInput('selectedFile', selectedFile); + const emitSpy = jest.spyOn(comp.onUpdateFeedback, 'emit'); + + fixture.detectChanges(); + + comp.feedbackInternal.set([]); + + comp.refreshFeedback(selectedFile); + + expect(comp.feedbackInternal()).toEqual([]); + + expect(emitSpy).not.toHaveBeenCalled(); + }); + + it('should display feedback(with multiple widgets at the same line) when viewing a tutor assessment', fakeAsync(() => { + const addLineWidgetStub = jest.spyOn(comp.editor(), 'addLineWidget').mockImplementation(); + const selectFileInEditorStub = jest.spyOn(comp, 'selectFileInEditor').mockImplementation(); + fixture.componentRef.setInput('isTutorAssessment', true); + fixture.componentRef.setInput('selectedFile', 'file2.java'); + fixture.componentRef.setInput('feedbacks', exampleFeedbacks); + TestBed.flushEffects(); + fixture.detectChanges(); + // Use .then() here instead of await so fakeAsync does not break. + comp.ngOnChanges({ selectedFile: new SimpleChange(undefined, 'file2', false) }).then(() => { + // Rendering of the feedback items happens after one tick to allow the renderer to catch up with the DOM nodes. + tick(1); + expect(addLineWidgetStub).toHaveBeenCalledTimes(2); + expect(addLineWidgetStub).toHaveBeenNthCalledWith(1, 10, `feedback-3`, document.createElement('div')); + expect(addLineWidgetStub).toHaveBeenNthCalledWith(2, 10, `feedback-4`, document.createElement('div')); + expect(getInlineFeedbackNodeForManualFeedbackStub).toHaveBeenCalledTimes(2); + expect(selectFileInEditorStub).toHaveBeenCalledOnce(); + }); + })); }); diff --git a/src/main/webapp/app/programming/shared/code-editor/monaco/code-editor-monaco.component.ts b/src/main/webapp/app/programming/shared/code-editor/monaco/code-editor-monaco.component.ts index 7f6ce039c5fc..46312b1f817b 100644 --- a/src/main/webapp/app/programming/shared/code-editor/monaco/code-editor-monaco.component.ts +++ b/src/main/webapp/app/programming/shared/code-editor/monaco/code-editor-monaco.component.ts @@ -19,19 +19,19 @@ import { RepositoryFileService } from 'app/programming/shared/services/repositor import { LocalStorageService } from 'ngx-webstorage'; import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; import { firstValueFrom, timeout } from 'rxjs'; -import { FEEDBACK_SUGGESTION_ACCEPTED_IDENTIFIER, FEEDBACK_SUGGESTION_IDENTIFIER, Feedback } from 'app/assessment/shared/entities/feedback.model'; +import { FEEDBACK_SUGGESTION_ACCEPTED_IDENTIFIER, FEEDBACK_SUGGESTION_IDENTIFIER, Feedback, FeedbackType } from 'app/assessment/shared/entities/feedback.model'; import { Course } from 'app/core/course/shared/entities/course.model'; import { CodeEditorTutorAssessmentInlineFeedbackComponent } from 'app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/code-editor-tutor-assessment-inline-feedback.component'; import { fromPairs, pickBy } from 'lodash-es'; -import { CodeEditorTutorAssessmentInlineFeedbackSuggestionComponent } from 'app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/suggestion/code-editor-tutor-assessment-inline-feedback-suggestion.component'; +import { CodeEditorTutorAssessmentInlineFeedbackSuggestionComponent } from 'app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback-suggestion.component'; import { MonacoEditorLineHighlight } from 'app/shared/monaco-editor/model/monaco-editor-line-highlight.model'; -import { FileTypeService } from 'app/programming/shared/services/file-type.service'; +import { FileTypeService } from 'app/programming/service/file-type.service'; import { EditorPosition } from 'app/shared/monaco-editor/model/actions/monaco-editor.util'; import { CodeEditorHeaderComponent } from 'app/programming/manage/code-editor/header/code-editor-header.component'; import { TranslateDirective } from 'app/shared/language/translate.directive'; -import { CodeEditorRepositoryFileService, ConnectionError } from 'app/programming/shared/code-editor/services/code-editor-repository.service'; +import { CodeEditorRepositoryFileService, ConnectionError } from 'app/programming/shared/code-editor/service/code-editor-repository.service'; import { CommitState, CreateFileChange, DeleteFileChange, EditorState, FileChange, FileType, RenameFileChange } from '../model/code-editor.model'; -import { CodeEditorFileService } from 'app/programming/shared/code-editor/services/code-editor-file.service'; +import { CodeEditorFileService } from 'app/programming/shared/code-editor/service/code-editor-file.service'; type FileSession = { [fileName: string]: { code: string; cursor: EditorPosition; scrollTop: number; loadingError: boolean } }; type FeedbackWithLineAndReference = Feedback & { line: number; reference: string }; @@ -251,7 +251,7 @@ export class CodeEditorMonacoComponent implements OnChanges { addNewFeedback(lineNumber: number): void { // TODO for a follow-up: in the future, there might be multiple feedback items on the same line. const lineNumberZeroBased = lineNumber - 1; - if (!this.getInlineFeedbackNode(lineNumberZeroBased)) { + if (!this.getInlineFeedbackNodeForManualFeedback(lineNumberZeroBased)) { this.newFeedbackLines.set([...this.newFeedbackLines(), lineNumberZeroBased]); this.renderFeedbackWidgets(lineNumberZeroBased); } @@ -305,10 +305,11 @@ export class CodeEditorMonacoComponent implements OnChanges { * @param feedback The feedback item of the feedback suggestion. */ acceptSuggestion(feedback: Feedback): void { - this.feedbackSuggestionsInternal.set(this.feedbackSuggestionsInternal().filter((f) => f !== feedback)); + const originalFeedbackObject = this.feedbackSuggestionsInternal().find((f) => Feedback.areIdentical(feedback, f))!; + this.feedbackSuggestionsInternal.set(this.feedbackSuggestionsInternal().filter((f) => !Feedback.areIdentical(feedback, f))); feedback.text = (feedback.text ?? FEEDBACK_SUGGESTION_IDENTIFIER).replace(FEEDBACK_SUGGESTION_IDENTIFIER, FEEDBACK_SUGGESTION_ACCEPTED_IDENTIFIER); this.updateFeedback(feedback); - this.onAcceptSuggestion.emit(feedback); + this.onAcceptSuggestion.emit(originalFeedbackObject); } /** @@ -316,9 +317,10 @@ export class CodeEditorMonacoComponent implements OnChanges { * @param feedback The feedback item of the feedback suggestion. */ discardSuggestion(feedback: Feedback): void { - this.feedbackSuggestionsInternal.set(this.feedbackSuggestionsInternal().filter((f) => f !== feedback)); + const originalFeedbackObject = this.feedbackSuggestionsInternal().find((f) => Feedback.areIdentical(feedback, f))!; + this.feedbackSuggestionsInternal.set(this.feedbackSuggestionsInternal().filter((f) => !Feedback.areIdentical(feedback, f))); this.renderFeedbackWidgets(); - this.onDiscardSuggestion.emit(feedback); + this.onDiscardSuggestion.emit(originalFeedbackObject); } /** @@ -331,29 +333,48 @@ export class CodeEditorMonacoComponent implements OnChanges { this.changeDetectorRef.detectChanges(); setTimeout(() => { this.editor().disposeWidgets(); + const feedbackMap = new Map(); + for (const feedback of this.filterFeedbackForSelectedFile([...this.feedbackInternal(), ...this.feedbackSuggestionsInternal()])) { - this.addLineWidgetWithFeedback(feedback); + const line = Feedback.getReferenceLine(feedback); + if (line === undefined) { + throw new Error('No line found for feedback ' + feedback.id); + } + + if (!feedbackMap.has(line)) { + feedbackMap.set(line, []); + } + feedbackMap.get(line)!.push(feedback); + } + + for (const [lineId, feedbackItems] of feedbackMap.entries()) { + this.addLineWidgetWithFeedback(feedbackItems, lineId); } // New, unsaved feedback has no associated object yet. for (const line of this.newFeedbackLines()) { - const feedbackNode = this.getInlineFeedbackNodeOrElseThrow(line); - this.editor().addLineWidget(line + 1, 'feedback-new-' + line, feedbackNode); + const feedbackNode = this.getInlineFeedbackNodeForManualFeedback(line); + if (feedbackNode) { + this.editor().addLineWidget(line + 1, 'feedback-new-' + line, feedbackNode); + } } // Focus the text area of the widget on the specified line if available. if (lineOfWidgetToFocus !== undefined) { - this.getInlineFeedbackNode(lineOfWidgetToFocus)?.querySelector('#feedback-textarea')?.focus(); + this.getInlineFeedbackNodeForManualFeedback(lineOfWidgetToFocus)?.querySelector('#feedback-textarea')?.focus(); } }, 0); } /** - * Retrieves the feedback node currently rendered at the specified line and throws an error if it is not available. + * Retrieves the feedback node that corresponds to a given feedback object and throws an error if it is not available. * @param line The line (0-based) for which to retrieve the feedback node. + * @param feedback The object that is of interest. */ - getInlineFeedbackNodeOrElseThrow(line: number): HTMLElement { - const element = this.getInlineFeedbackNode(line); + getInlineFeedbackNodeOrElseThrow(feedback: Feedback | undefined, line: number): HTMLElement { + const element = [...this.inlineFeedbackComponents(), ...this.inlineFeedbackSuggestionComponents()].find( + (comp) => comp.codeLine === line && comp.feedback?.type === feedback?.type && comp.feedback?.id === feedback?.id && comp.feedback?.detailText === feedback?.detailText, + )?.elementRef?.nativeElement; if (!element) { throw new Error('No feedback node found at line ' + line); } @@ -361,23 +382,24 @@ export class CodeEditorMonacoComponent implements OnChanges { } /** - * Retrieves the feedback node currently rendered at the specified line, or undefined if it is not available. + * Retrieves manually created feedback node currently rendered at the specified line, or undefined if it is not available. * @param line The line (0-based) for which to retrieve the feedback node. */ - getInlineFeedbackNode(line: number): HTMLElement | undefined { - return [...this.inlineFeedbackComponents(), ...this.inlineFeedbackSuggestionComponents()].find((comp) => comp.codeLine === line)?.elementRef?.nativeElement; + getInlineFeedbackNodeForManualFeedback(line: number): HTMLElement | undefined { + // Feedback suggestions also have type manual + // New feedback has type undefined, see the setter in tutor inline feedback component + return [...this.inlineFeedbackComponents(), ...this.inlineFeedbackSuggestionComponents()].find( + (comp) => comp.codeLine === line && (!comp.feedback.type || comp.feedback.type === FeedbackType.MANUAL), + )?.elementRef?.nativeElement; } - private addLineWidgetWithFeedback(feedback: Feedback): void { - const line = Feedback.getReferenceLine(feedback); - if (line === undefined) { - throw new Error('No line found for feedback ' + feedback.id); - } - // TODO: In the future, there may be more than one feedback node per line. The ID should be unique. - const feedbackNode = this.getInlineFeedbackNodeOrElseThrow(line); - // Feedback is stored with 0-based lines, but the lines of the Monaco editor used in Artemis are 1-based. We add 1 to correct this - const oneBasedLine = line + 1; - this.editor().addLineWidget(oneBasedLine, 'feedback-' + feedback.id + '-line-' + oneBasedLine, feedbackNode); + private addLineWidgetWithFeedback(feedbacks: Feedback[], line: number): void { + feedbacks.forEach((feedback) => { + const feedbackNode = this.getInlineFeedbackNodeOrElseThrow(feedback, line); + // Feedback is stored with 0-based lines, but the lines of the Monaco editor used in Artemis are 1-based. We add 1 to correct this + const oneBasedLine = line + 1; + this.editor().addLineWidget(oneBasedLine, 'feedback-' + feedback.id + '-line-' + oneBasedLine, feedbackNode); + }); } /** @@ -464,4 +486,20 @@ export class CodeEditorMonacoComponent implements OnChanges { getLineHighlights(): MonacoEditorLineHighlight[] { return this.editor().getLineHighlights(); } + + refreshFeedback(fileName: string) { + if (this.selectedFile() !== fileName) { + return; + } + const currentFeedback = new Set(this.feedbackInternal()); + const closedFeedback = this.feedbacks().filter((feedback) => feedback.reference && feedback.reference.startsWith('file:' + fileName) && !currentFeedback.has(feedback)); + + if (closedFeedback.length > 0) { + this.feedbackInternal.set([...this.feedbackInternal(), ...closedFeedback]); + this.onUpdateFeedback.emit(this.feedbackInternal()); + this.renderFeedbackWidgets(); + } +import { CodeEditorTutorAssessmentInlineFeedbackSuggestionComponent } from 'app/programming/manage/assess/code-editor-tutor-assessment-inline-feedback/suggestion/code-editor-tutor-assessment-inline-feedback-suggestion.component'; +import { FileTypeService } from 'app/programming/shared/services/file-type.service'; + } } diff --git a/src/main/webapp/i18n/de/editor.json b/src/main/webapp/i18n/de/editor.json index 0c2664a671fb..7cfce5221f83 100644 --- a/src/main/webapp/i18n/de/editor.json +++ b/src/main/webapp/i18n/de/editor.json @@ -47,6 +47,7 @@ "fileLoadedSuccess": "Datei erfolgreich geladen", "fetchingParticipation": "Deine Teilnahme wird geladen, bitte warte einen Moment...", "tabSize": "Tabulatorweite:", + "showReopenFeedbackHint": "Du kannst Feedback erneut öffnen, indem du auf die entsprechende Schaltfläche im Dateibrowser für die gewünschte Datei klickst.", "conflict": { "resetRepository": "Setze Dein Repository zurück, um Deine Teilnahme fortzusetzen", "resolveConflict": "Konflikt beheben", @@ -77,9 +78,11 @@ "renameFolderDisabledTooltip": "Um Verzeichnisse umzubenennen, deaktiviere das Zusammenfassen von Verzeichnissen in der obigen Leiste.", "deleteFolder": "Verzeichnis löschen", "deleteFile": "Datei löschen", + "reopenFeedback": "Feedback erneut anzeigen", "compressTree": "Leere Verzeichnisse zusammenfassen", "fileBadgeTooltips": { - "feedbackSuggestions": "Anzahl von Feedback-Vorschlägen in dieser Datei / diesem Ordner" + "feedbackSuggestions": "Anzahl von Feedback-Vorschlägen in dieser Datei / diesem Ordner", + "preliminaryFeedback": "Anzahl von vorläufigen Feedback Elementen in dieser Datei / diesem Ordner" } }, "errors": { diff --git a/src/main/webapp/i18n/en/editor.json b/src/main/webapp/i18n/en/editor.json index 5da0d1b80dde..60d7f99d9222 100644 --- a/src/main/webapp/i18n/en/editor.json +++ b/src/main/webapp/i18n/en/editor.json @@ -47,6 +47,7 @@ "fileLoadedSuccess": "File loaded successfully", "fetchingParticipation": "Your participation is being loaded, please wait a moment...", "tabSize": "Tab size:", + "showReopenFeedbackHint": "You can reopen feedback by clicking the corresponding button in the file browser for the desired file.", "conflict": { "resetRepository": "Reset your repository to continue with your participation", "resolveConflict": "Resolve conflict", @@ -77,9 +78,11 @@ "renameFolderDisabledTooltip": "To rename this folder, deactivate the combining of folders in the toolbar above.", "deleteFolder": "Delete folder", "deleteFile": "Delete file", + "reopenFeedback": "Reopen feedback", "compressTree": "Combine empty folders", "fileBadgeTooltips": { - "feedbackSuggestions": "Number of feedback suggestions in this file/folder" + "feedbackSuggestions": "Number of feedback suggestions in this file/folder", + "preliminaryFeedback": "Number of preliminary feedback items in this file/folder" } }, "errors": { diff --git a/src/test/javascript/spec/helpers/dialog-test-helpers.ts b/src/test/javascript/spec/helpers/dialog-test-helpers.ts deleted file mode 100644 index b03774d4b62e..000000000000 --- a/src/test/javascript/spec/helpers/dialog-test-helpers.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { AbstractDialogComponent } from 'app/communication/course-conversations-components/abstract-dialog.component'; -import { ComponentFixture } from '@angular/core/testing'; - -type RequiredInputs = { - [key: string]: any; -}; - -export function initializeDialog(component: AbstractDialogComponent, fixture: ComponentFixture, requiredInputs: RequiredInputs) { - component.initialize(); - fixture.detectChanges(); - expect(component.isInitialized).toBeFalse(); - - // expect console.err not to be called - // loop over required inputs and set on component - Object.keys(requiredInputs).forEach((key) => { - component[key as keyof AbstractDialogComponent] = requiredInputs[key]; - }); - - component.initialize(); - fixture.detectChanges(); - expect(component.isInitialized).toBeTrue(); -} diff --git a/src/test/javascript/spec/helpers/mocks/activated-route/mock-activated-route-query-param-map.ts b/src/test/javascript/spec/helpers/mocks/activated-route/mock-activated-route-query-param-map.ts deleted file mode 100644 index 4d412844cbcf..000000000000 --- a/src/test/javascript/spec/helpers/mocks/activated-route/mock-activated-route-query-param-map.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Provider } from '@angular/core'; -import { ActivatedRoute, convertToParamMap } from '@angular/router'; -import { BehaviorSubject, of } from 'rxjs'; - -export function mockedActivatedRoute(params: any, queryParams: any = {}, data: any = {}, parentParams: any = {}, parentParentParams: any = {}): Provider { - return { - provide: ActivatedRoute, - useValue: { - data: of(data), - paramMap: new BehaviorSubject(convertToParamMap(params)), - queryParamMap: new BehaviorSubject(convertToParamMap(queryParams)), - parent: { - paramMap: new BehaviorSubject(convertToParamMap(parentParams)), - parent: { - paramMap: new BehaviorSubject(convertToParamMap(parentParentParams)), - }, - }, - }, - }; -} diff --git a/src/test/javascript/spec/helpers/mocks/activated-route/mock-activated-route-snapshot.ts b/src/test/javascript/spec/helpers/mocks/activated-route/mock-activated-route-snapshot.ts deleted file mode 100644 index f6addee5cb42..000000000000 --- a/src/test/javascript/spec/helpers/mocks/activated-route/mock-activated-route-snapshot.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Provider } from '@angular/core'; -import { ActivatedRouteSnapshot } from '@angular/router'; - -export function mockedActivatedRouteSnapshot(snapshotPath: any): Provider { - return { - provide: ActivatedRouteSnapshot, - useValue: { - routeConfig: { path: snapshotPath }, - }, - }; -} diff --git a/src/test/javascript/spec/helpers/mocks/activated-route/mock-activated-route-with-subjects.ts b/src/test/javascript/spec/helpers/mocks/activated-route/mock-activated-route-with-subjects.ts deleted file mode 100644 index 2a766c7b24c2..000000000000 --- a/src/test/javascript/spec/helpers/mocks/activated-route/mock-activated-route-with-subjects.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Subject } from 'rxjs'; -import { convertToParamMap, Params } from '@angular/router'; - -export class MockActivatedRouteWithSubjects { - private subject = new Subject(); - params = this.subject; - snapshot = { paramMap: convertToParamMap({ courseId: '1' }) }; - setSubject = (subject: Subject) => { - this.params = subject; - }; -} diff --git a/src/test/javascript/spec/helpers/mocks/activated-route/mock-activated-route.ts b/src/test/javascript/spec/helpers/mocks/activated-route/mock-activated-route.ts deleted file mode 100644 index f5dcfeb2070b..000000000000 --- a/src/test/javascript/spec/helpers/mocks/activated-route/mock-activated-route.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { ActivatedRoute, Data, Params } from '@angular/router'; -import { ReplaySubject } from 'rxjs'; - -export class MockActivatedRoute extends ActivatedRoute { - private queryParamsSubject = new ReplaySubject(1); - private paramSubject = new ReplaySubject(1); - private dataSubject = new ReplaySubject(1); - - constructor(parameters?: any) { - super(); - this.queryParams = this.queryParamsSubject.asObservable(); - this.params = this.paramSubject.asObservable(); - this.data = this.dataSubject.asObservable(); - this.setParameters(parameters); - } - - setParameters(parameters: Params): void { - this.queryParamsSubject.next(parameters); - this.paramSubject.next(parameters); - this.dataSubject.next({ - ...parameters, - defaultSort: 'id,desc', - pagingParams: { - page: 10, - ascending: false, - predicate: 'id', - }, - }); - } - - get root(): ActivatedRoute { - return this; - } - - get firstChild(): ActivatedRoute { - return this; - } - get parent(): ActivatedRoute { - return this; - } -} diff --git a/src/test/javascript/spec/helpers/mocks/directive/mock-has-any-authority.directive.ts b/src/test/javascript/spec/helpers/mocks/directive/mock-has-any-authority.directive.ts deleted file mode 100644 index c712b1b0d1cb..000000000000 --- a/src/test/javascript/spec/helpers/mocks/directive/mock-has-any-authority.directive.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; - -@Directive({ - selector: '[jhiHasAnyAuthority]', - exportAs: 'jhiHasAnyAuthority', -}) -export class MockHasAnyAuthorityDirective { - constructor( - private templateRef: TemplateRef, - private viewContainerRef: ViewContainerRef, - ) {} - - @Input() - set jhiHasAnyAuthority(_value: string | string[]) { - this.updateView(); - } - - private updateView(): void { - this.viewContainerRef.createEmbeddedView(this.templateRef); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/directive/mock-jhi-translate-directive.directive.ts b/src/test/javascript/spec/helpers/mocks/directive/mock-jhi-translate-directive.directive.ts deleted file mode 100644 index cdb29e170177..000000000000 --- a/src/test/javascript/spec/helpers/mocks/directive/mock-jhi-translate-directive.directive.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Directive, Input } from '@angular/core'; - -@Directive({ - selector: '[jhiTranslate]', -}) -export class MockJhiTranslateDirective { - @Input() jhiTranslate: string; -} diff --git a/src/test/javascript/spec/helpers/mocks/directive/mock-router-link.directive.ts b/src/test/javascript/spec/helpers/mocks/directive/mock-router-link.directive.ts deleted file mode 100644 index b75d7baa77c3..000000000000 --- a/src/test/javascript/spec/helpers/mocks/directive/mock-router-link.directive.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Directive, HostListener, Input, Optional } from '@angular/core'; -import { Router } from '@angular/router'; - -@Directive({ - selector: '[routerLink]', -}) -export class MockRouterLinkDirective { - @Input('routerLink') data: any; - - constructor(@Optional() private router: Router) {} - - @HostListener('click') - onClick() { - this.router.navigateByUrl(this.data); - } -} - -@Directive({ - selector: '[routerLinkActiveOptions]', -}) -export class MockRouterLinkActiveOptionsDirective { - @Input('routerLinkActiveOptions') data: any; -} - -@Directive({ - selector: '[queryParams]', -}) -export class MockQueryParamsDirective { - @Input('queryParams') data: any; -} diff --git a/src/test/javascript/spec/helpers/mocks/directive/mock-translate-values.directive.ts b/src/test/javascript/spec/helpers/mocks/directive/mock-translate-values.directive.ts deleted file mode 100644 index 79143047a1fa..000000000000 --- a/src/test/javascript/spec/helpers/mocks/directive/mock-translate-values.directive.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Directive, Input } from '@angular/core'; - -@Directive({ selector: '[translateValues]' }) -export class MockTranslateValuesDirective { - @Input('translateValues') data: any; -} diff --git a/src/test/javascript/spec/helpers/mocks/iris/mock-settings.ts b/src/test/javascript/spec/helpers/mocks/iris/mock-settings.ts deleted file mode 100644 index 8c87bfdcb355..000000000000 --- a/src/test/javascript/spec/helpers/mocks/iris/mock-settings.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { IrisVariant } from 'app/iris/shared/entities/settings/iris-variant'; -import { - IrisChatSubSettings, - IrisCompetencyGenerationSubSettings, - IrisCourseChatSubSettings, - IrisLectureChatSubSettings, - IrisLectureIngestionSubSettings, - IrisTextExerciseChatSubSettings, -} from 'app/iris/shared/entities/settings/iris-sub-settings.model'; -import { IrisGlobalSettings } from 'app/iris/shared/entities/settings/iris-settings.model'; - -export function mockSettings() { - const mockChatSettings = new IrisChatSubSettings(); - mockChatSettings.id = 1; - mockChatSettings.enabled = true; - mockChatSettings.disabledProactiveEvents = []; - const mockTextExerciseChatSettings = new IrisTextExerciseChatSubSettings(); - mockTextExerciseChatSettings.id = 13; - mockTextExerciseChatSettings.enabled = true; - const mockCourseChatSettings = new IrisCourseChatSubSettings(); - mockCourseChatSettings.id = 3; - mockCourseChatSettings.enabled = true; - const mockLectureIngestionSettings = new IrisLectureIngestionSubSettings(); - mockLectureIngestionSettings.id = 7; - mockLectureIngestionSettings.enabled = true; - mockLectureIngestionSettings.autoIngestOnLectureAttachmentUpload = true; - const mockCompetencyGenerationSettings = new IrisCompetencyGenerationSubSettings(); - mockCompetencyGenerationSettings.id = 5; - mockCompetencyGenerationSettings.enabled = false; - - const mockIrisLectureSettings = new IrisLectureChatSubSettings(); - mockIrisLectureSettings.id = 42; - mockIrisLectureSettings.enabled = true; - - const irisSettings = new IrisGlobalSettings(); - irisSettings.id = 1; - irisSettings.irisChatSettings = mockChatSettings; - irisSettings.irisTextExerciseChatSettings = mockTextExerciseChatSettings; - irisSettings.irisCourseChatSettings = mockCourseChatSettings; - irisSettings.irisCompetencyGenerationSettings = mockCompetencyGenerationSettings; - irisSettings.irisLectureIngestionSettings = mockLectureIngestionSettings; - irisSettings.irisLectureChatSettings = mockIrisLectureSettings; - return irisSettings; -} - -export function mockEmptySettings() { - const irisSettings = new IrisGlobalSettings(); - irisSettings.id = 1; - return irisSettings; -} - -export function mockVariants() { - return [ - { - id: '1', - name: 'Model 1', - description: 'Model 1 Description', - }, - { - id: '2', - name: 'Model 2', - description: 'Model 2 Description', - }, - ] as IrisVariant[]; -} diff --git a/src/test/javascript/spec/helpers/mocks/mock-instance.helper.ts b/src/test/javascript/spec/helpers/mocks/mock-instance.helper.ts deleted file mode 100644 index 39c79a6c226c..000000000000 --- a/src/test/javascript/spec/helpers/mocks/mock-instance.helper.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { MockInstance } from 'ng-mocks'; -import { CodeEditorMonacoComponent } from 'app/programming/shared/code-editor/monaco/code-editor-monaco.component'; -import { signal } from '@angular/core'; -import { ThemeSwitchComponent } from 'app/core/theme/theme-switch.component'; - -/* - * This file contains mock instances for the tests where they would otherwise fail due to the use of a signal-based viewChild or contentChild with MockComponent(SomeComponent). - * This is a workaround for https://github.com/help-me-mom/ng-mocks/issues/8634. Once the issue is resolved, this file and the functions it defines can be removed. - */ - -/** - * Overwrites the signal-based viewChild queries of the CodeEditorMonacoComponent so it can be mocked using MockComponent. - * Workaround for https://github.com/help-me-mom/ng-mocks/issues/8634. - */ -export function mockCodeEditorMonacoViewChildren() { - MockInstance.scope('case'); - MockInstance(CodeEditorMonacoComponent, () => ({ - editor: signal({}), - inlineFeedbackComponents: signal([]), - inlineFeedbackSuggestionComponents: signal([]), - })); -} - -export function mockThemeSwitcherComponentViewChildren() { - MockInstance.scope('case'); - MockInstance(ThemeSwitchComponent, () => ({ - popover: signal({}), - })); -} diff --git a/src/test/javascript/spec/helpers/mocks/mock-router.ts b/src/test/javascript/spec/helpers/mocks/mock-router.ts deleted file mode 100644 index 9d04c0e57d04..000000000000 --- a/src/test/javascript/spec/helpers/mocks/mock-router.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Observable, Subject } from 'rxjs'; -import { NavigationEnd, RouterEvent, RouterState, UrlTree } from '@angular/router'; - -// When using the spies, bear in mind jest.resetAllMocks does not affect them, they need to be reset manually -export class MockRouter { - url = '/'; - navigateByUrl = jest.fn().mockReturnValue(true); - navigate = jest.fn().mockReturnValue(Promise.resolve(true)); - routerState: RouterState; - createUrlTree = jest.fn().mockReturnValue({ path: 'testValue' } as unknown as UrlTree); - serializeUrl = jest.fn().mockReturnValue('testValue'); - - eventSubject: Subject = new Subject(); - - setRouterState(routerState: RouterState) { - this.routerState = routerState; - } - - get events(): Observable { - return this.eventSubject.asObservable(); - } - - setUrl(url: string) { - this.url = url; - this.eventSubject.next(new NavigationEnd(0, url, url)); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/programming-exercise-creation-config-mock.ts b/src/test/javascript/spec/helpers/mocks/programming-exercise-creation-config-mock.ts deleted file mode 100644 index 00dc520d61a3..000000000000 --- a/src/test/javascript/spec/helpers/mocks/programming-exercise-creation-config-mock.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { ProgrammingExerciseCreationConfig } from 'app/programming/manage/update/programming-exercise-creation-config'; -import { Observable } from 'rxjs'; -import { ProgrammingLanguage, ProjectType } from 'app/programming/shared/entities/programming-exercise.model'; -import { ExerciseCategory } from 'app/exercise/shared/entities/exercise/exercise-category.model'; -import { AuxiliaryRepository } from 'app/programming/shared/entities/programming-exercise-auxiliary-repository-model'; - -/* eslint-disable @typescript-eslint/no-unused-vars */ -export const programmingExerciseCreationConfigMock: ProgrammingExerciseCreationConfig = { - auxiliaryRepositoriesSupported: false, - auxiliaryRepositoryDuplicateDirectories: false, - auxiliaryRepositoryDuplicateNames: false, - checkoutSolutionRepositoryAllowed: false, - buildPlanLoaded: false, - customBuildPlansSupported: '', - exerciseCategories: [], - existingCategories: [], - hasUnsavedChanges: false, - inProductionEnvironment: false, - invalidDirectoryNamePattern: new RegExp('(?!)'), - invalidRepositoryNamePattern: new RegExp('(?!)'), - isEdit: false, - isExamMode: false, - isImportFromExistingExercise: false, - isImportFromFile: false, - maxPenaltyPattern: '', - modePickerOptions: [], - onProgrammingLanguageChange(language: ProgrammingLanguage): ProgrammingLanguage { - return language; - }, - onProjectTypeChange(projectType: ProjectType): ProjectType { - return projectType; - }, - onRecreateBuildPlanOrUpdateTemplateChange(): void {}, - onStaticCodeAnalysisChanged(): void {}, - onWithDependenciesChanged(_withDependencies: boolean): boolean { - return false; - }, - packageNamePattern: '', - packageNameRequired: false, - problemStatementLoaded: false, - projectTypes: [], - recreateBuildPlanOrUpdateTemplateChange(): void {}, - recreateBuildPlans: false, - refreshAuxiliaryRepositoryChecks(): void {}, - rerenderSubject: new Observable(), - selectedProgrammingLanguage: ProgrammingLanguage.JAVA, - selectedProjectType: ProjectType.PLAIN_GRADLE, - sequentialTestRunsAllowed: false, - shortNamePattern: new RegExp('(?!)'), - showSummary: false, - staticCodeAnalysisAllowed: false, - supportedLanguages: [], - templateParticipationResultLoaded: false, - titleNamePattern: '', - updateCategories(_categories: ExerciseCategory[]): void {}, - updateCheckoutDirectory(_editedAuxiliaryRepository: AuxiliaryRepository): (newValue: any) => string | undefined { - return function (_p1: any) { - return 'dirName'; - }; - }, - updateRepositoryName(_auxiliaryRepository: AuxiliaryRepository): (newValue: any) => string | undefined { - return function (_p1: any) { - return 'repoName'; - }; - }, - updateTemplate: false, - validIdeSelection(): boolean | undefined { - return true; - }, - validOnlineIdeSelection(): boolean | undefined { - return true; - }, - withDependencies: false, -}; diff --git a/src/test/javascript/spec/helpers/mocks/service/dummy-paging-service.ts b/src/test/javascript/spec/helpers/mocks/service/dummy-paging-service.ts deleted file mode 100644 index 7c8f94fee2fe..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/dummy-paging-service.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { ExercisePagingService } from 'app/exercise/services/exercise-paging.service'; - -@Injectable({ providedIn: 'root' }) -export class DummyPagingService extends ExercisePagingService { - constructor(http: HttpClient) { - super(http, 'test'); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-account.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-account.service.ts deleted file mode 100644 index dff5b1f4fbea..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-account.service.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { of } from 'rxjs'; -import { Course } from 'app/core/course/shared/entities/course.model'; -import { IAccountService } from 'app/core/auth/account.service'; -import { User } from 'app/core/user/user.model'; -import { Exercise } from 'app/exercise/shared/entities/exercise/exercise.model'; - -export class MockAccountService implements IAccountService { - userIdentityValue: User | undefined; - - get userIdentity() { - return this.userIdentityValue; - } - set userIdentity(user: User | undefined) { - this.userIdentityValue = user; - } - - identity = () => Promise.resolve({ id: 99, login: 'admin' } as User); - getAndClearPrefilledUsername = () => 'prefilledUsername'; - setPrefilledUsername = (username: string) => ({}); - hasAnyAuthority = (authorities: any[]) => Promise.resolve(true); - hasAnyAuthorityDirect = (authorities: any[]) => authorities.length !== 0; - getAuthenticationState = () => of({ id: 99 } as User); - authenticate = (identity: User | undefined) => {}; - fetch = () => of({ body: { id: 99 } as User } as any); - updateLanguage = (languageKey: string) => of({}); - getImageUrl = () => 'blob'; - hasAuthority = (authority: string) => Promise.resolve(true); - isAtLeastTutorInCourse = (course: Course) => true; - isAtLeastEditorInCourse = (course: Course) => course.isAtLeastEditor!; - isAtLeastInstructorInCourse = (course: Course) => course.isAtLeastInstructor!; - isAtLeastTutorForExercise = (exercise?: Exercise) => true; - isAtLeastEditorForExercise = (exercise?: Exercise) => true; - isAtLeastInstructorForExercise = (exercise?: Exercise) => true; - setAccessRightsForExercise = (exercise?: Exercise) => ({}) as any; - setAccessRightsForCourse = (course?: Course) => ({}) as any; - setAccessRightsForExerciseAndReferencedCourse = (exercise?: Exercise) => {}; - setAccessRightsForCourseAndReferencedExercises = (course?: Course) => {}; - isAuthenticated = () => true; - isOwnerOfParticipation = () => true; - isAdmin = () => true; - save = (account: any) => ({}) as any; - getVcsAccessToken = (participationId: number) => of(); - createVcsAccessToken = (participationId: number) => of(); - getToolToken = () => of(); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-alert.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-alert.service.ts deleted file mode 100644 index ac758fff6ccd..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-alert.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Alert } from 'app/shared/service/alert.service'; - -export class MockAlertService { - success = (message: string) => ({}) as Alert; - error = (message: string) => ({}) as Alert; - addAlert = (alert: Alert) => {}; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-answer-post.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-answer-post.service.ts deleted file mode 100644 index 8278653700d5..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-answer-post.service.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { AnswerPost } from 'app/communication/shared/entities/answer-post.model'; -import { HttpResponse } from '@angular/common/http'; -import { metisCoursePosts } from '../../sample/metis-sample-data'; - -export class MockAnswerPostService { - create(courseId: number, answerPost: AnswerPost): Observable> { - return of({ body: answerPost }) as Observable>; - } - - update(courseId: number, answerPost: AnswerPost): Observable> { - return of({ body: answerPost }) as Observable>; - } - - delete(answerPost: AnswerPost): Observable> { - return of({ body: {} }) as Observable>; - } - - getSourceAnswerPostsByIds(courseId: number, answerPostIds: number[]): Observable { - const sourceAnswerPosts = metisCoursePosts.flatMap((post) => post.answers || []).filter((answerPost) => answerPostIds.includes(answerPost.id!)); - - return of(sourceAnswerPosts); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-artemis-server-date.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-artemis-server-date.service.ts deleted file mode 100644 index fc222ae1d9b2..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-artemis-server-date.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import dayjs from 'dayjs/esm'; - -export class MockArtemisServerDateService { - now(): dayjs.Dayjs { - return dayjs(); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-athena-service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-athena-service.ts deleted file mode 100644 index 357655558de2..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-athena-service.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { ProgrammingFeedbackSuggestion, TextFeedbackSuggestion } from 'app/assessment/shared/entities/feedback-suggestion.model'; - -export class MockAthenaService { - getTextFeedbackSuggestions(exerciseId: number, submissionId: number): Observable { - return of([] as TextFeedbackSuggestion[]); - } - - getProgrammingFeedbackSuggestions(exerciseId: number, submissionId: number): Observable { - return of([] as ProgrammingFeedbackSuggestion[]); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-athena.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-athena.service.ts deleted file mode 100644 index c3c90a04b23c..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-athena.service.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { ModelingFeedbackSuggestion, ProgrammingFeedbackSuggestion, TextFeedbackSuggestion } from 'app/assessment/shared/entities/feedback-suggestion.model'; - -export class MockAthenaService { - getProgrammingFeedbackSuggestions(exerciseId: number, submissionId: number): Observable { - return of([]); - } - getTextFeedbackSuggestions(exerciseId: number, submissionId: number): Observable { - return of([]); - } - getModelingFeedbackSuggestions(exerciseId: number, submissionId: number): Observable { - return of([]); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-attachment-units.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-attachment-units.service.ts deleted file mode 100644 index 68c1ce0b3c48..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-attachment-units.service.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { of } from 'rxjs'; -import { LectureUnitInformationDTO } from 'app/lecture/manage/lecture-units/attachment-units/attachment-units.component'; - -export class MockAttachmentUnitsService { - getSplitUnitsData = (lectureId: number, filename: string) => of({}); - - createUnits = (lectureId: number, filename: string, lectureUnitInformation: LectureUnitInformationDTO) => of({}); - - uploadSlidesForProcessing = (lectureId: number, file: File) => of({}); - - getSlidesToRemove = (lectureId: number, filename: string, keyPhrases: string) => of({}); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-auth-server-provider.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-auth-server-provider.service.ts deleted file mode 100644 index 2a95ffc60ed7..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-auth-server-provider.service.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Credentials, IAuthServerProvider } from 'app/core/auth/auth-jwt.service'; -import { EMPTY } from 'rxjs'; -import { of } from 'rxjs'; - -export class MockAuthServerProviderService implements IAuthServerProvider { - login = (credentials: Credentials) => of(EMPTY); - loginSAML2 = (rememberMe: boolean) => of(EMPTY); - logout = () => of(EMPTY); - clearCaches = () => of(undefined); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-build-plan.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-build-plan.service.ts deleted file mode 100644 index 478000612b8d..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-build-plan.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { of } from 'rxjs'; -import { BuildPlan } from 'app/programming/shared/entities/build-plan.model'; - -export class MockBuildPlanService { - getBuildPlan = (exerciseId: number) => of({}); - putBuildPlan = (exerciseId: number, buildPlan: BuildPlan) => of({}); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-cacheable-image.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-cacheable-image.service.ts deleted file mode 100644 index fa2a317dd2e2..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-cacheable-image.service.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { ICacheableImageService } from 'app/shared/image/cacheable-image.service'; - -export class MockCacheableImageService implements ICacheableImageService { - loadCachedLocalStorage(url: string): Observable { - return of(); - } - - loadCachedSessionStorage(url: string): Observable { - return of(); - } - - loadWithoutCache(url: string): Observable { - return of(); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-clipboard-item.ts b/src/test/javascript/spec/helpers/mocks/service/mock-clipboard-item.ts deleted file mode 100644 index 36e72a69237a..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-clipboard-item.ts +++ /dev/null @@ -1,9 +0,0 @@ -export class MockClipboardItem { - types: string[]; - - getType(_type: string): Promise { - return Promise.resolve(new Blob()); - } - - presentationStyle: PresentationStyle; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-code-editor-build-log.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-code-editor-build-log.service.ts deleted file mode 100644 index beeb6ed7a8e8..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-code-editor-build-log.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { of } from 'rxjs'; -import { BuildLogEntry } from 'app/buildagent/shared/entities/build-log.model'; -import { IBuildLogService } from 'app/programming/shared/services/build-log.service'; - -export class MockCodeEditorBuildLogService implements IBuildLogService { - getBuildLogs = () => of([] as BuildLogEntry[]); - getTestRepositoryBuildLogs = (participationId: number) => of([] as BuildLogEntry[]); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-code-editor-conflict-state.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-code-editor-conflict-state.service.ts deleted file mode 100644 index 5a6b57bae2df..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-code-editor-conflict-state.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { of } from 'rxjs'; -import { IConflictStateService } from 'app/programming/shared/code-editor/services/code-editor-conflict-state.service'; -import { GitConflictState } from 'app/programming/shared/code-editor/model/code-editor.model'; - -export class MockCodeEditorConflictStateService implements IConflictStateService { - subscribeConflictState = () => of(GitConflictState.OK); - notifyConflictState = (gitConflictState: GitConflictState) => {}; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-code-editor-repository-file.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-code-editor-repository-file.service.ts deleted file mode 100644 index ec06e4155613..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-code-editor-repository-file.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from '@angular/core'; -import { EMPTY, of } from 'rxjs'; -import { ICodeEditorRepositoryFileService } from 'app/programming/shared/code-editor/services/code-editor-repository.service'; - -@Injectable({ providedIn: 'root' }) -export class MockCodeEditorRepositoryFileService implements ICodeEditorRepositoryFileService { - getRepositoryContent = () => EMPTY; - - getRepositoryContentForPlagiarismView = () => EMPTY; - getFile = (fileName: string) => EMPTY; - - getFileForPlagiarismView = (fileName: string) => EMPTY; - createFile = (fileName: string) => EMPTY; - createFolder = (fileName: string) => EMPTY; - updateFileContent = (fileName: string) => EMPTY; - updateFiles = (fileUpdates: Array<{ fileName: string; fileContent: string }>) => of({ fileName: undefined }); - renameFile = (fileName: string) => EMPTY; - deleteFile = (fileName: string) => EMPTY; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-code-editor-repository.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-code-editor-repository.service.ts deleted file mode 100644 index c01a612c520c..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-code-editor-repository.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EMPTY, of } from 'rxjs'; -import { ICodeEditorRepositoryService } from 'app/programming/shared/code-editor/services/code-editor-repository.service'; - -export class MockCodeEditorRepositoryService implements ICodeEditorRepositoryService { - getStatus = () => of({ repositoryStatus: 'CLEAN' }); - commit = () => EMPTY; - pull = () => EMPTY; - resetRepository = () => EMPTY; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-complaint.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-complaint.service.ts deleted file mode 100644 index b3989b7674b9..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-complaint.service.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { EntityResponseType, EntityResponseTypeArray, IComplaintService } from 'app/assessment/shared/services/complaint.service'; -import { User } from 'app/core/user/user.model'; -import { Result } from 'app/exercise/shared/entities/result/result.model'; -import { Observable, of } from 'rxjs'; -import { Complaint, ComplaintType } from 'app/assessment/shared/entities/complaint.model'; -import { Exercise } from 'app/exercise/shared/entities/exercise/exercise.model'; -import { HttpResponse } from '@angular/common/http'; - -const complaintObject: Complaint = { - complaintType: ComplaintType.COMPLAINT, - accepted: undefined, - complaintText: 'I think my answer was better than 2', - id: 123, - result: new Result(), - student: new User(), -}; -const feedbackRequestObject: Complaint = { - complaintType: ComplaintType.MORE_FEEDBACK, - accepted: true, - complaintText: 'I think my answer was better than 2', - id: 111, - result: new Result(), - student: new User(), -}; - -export const MockComplaintResponse: HttpResponse = { - body: complaintObject, -} as HttpResponse; - -export const MockComplaintResponse2: HttpResponse = { - body: feedbackRequestObject, -} as HttpResponse; - -export const MockComplaintArrayResponse: HttpResponse = { - body: [complaintObject, feedbackRequestObject], -} as HttpResponse; - -export class MockComplaintService implements IComplaintService { - create(complaint: Complaint): Observable { - return of(); - } - find(complaintId: number): Observable { - return of(); - } - findBySubmissionId(submissionId: number): Observable { - if (submissionId === 111) { - return of(MockComplaintResponse2); - } - return of(MockComplaintResponse); - } - - findAllByCourseId(courseId: number, complaintType: ComplaintType): Observable { - return of(MockComplaintArrayResponse); - } - - findAllByCourseIdAndExamId(courseId: number, examId: number): Observable { - return of(MockComplaintArrayResponse); - } - - findAllByExerciseId(exerciseId: number, complaintType: ComplaintType): Observable { - return of(MockComplaintArrayResponse); - } - - findAllByTutorIdForCourseId(tutorId: number, courseId: number, complaintType: ComplaintType): Observable { - return of(MockComplaintArrayResponse); - } - - findAllByTutorIdForExerciseId(tutorId: number, exerciseId: number, complaintType: ComplaintType): Observable { - return of(MockComplaintArrayResponse); - } - - getComplaintsForTestRun(exerciseId: number): Observable { - return of(MockComplaintArrayResponse); - } - - isComplaintLocked(complaint: Complaint): boolean | undefined { - return undefined; - } - - isComplaintLockedByLoggedInUser(complaint: Complaint): boolean | undefined { - return undefined; - } - - isComplaintLockedForLoggedInUser(complaint: Complaint, exercise: Exercise): boolean | undefined { - return undefined; - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-conversation.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-conversation.service.ts deleted file mode 100644 index 8a63a4e02f0a..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-conversation.service.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { HttpResponse } from '@angular/common/http'; -import { Observable, of } from 'rxjs'; -import { UserPublicInfoDTO } from 'app/core/user/user.model'; -import { ConversationDTO } from 'app/communication/shared/entities/conversation/conversation.model'; - -export class MockConversationService { - getResponsibleUsersForCodeOfConduct(courseId: number): Observable> { - return of(new HttpResponse({ body: [] })); - } - - getConversationsOfUser(courseId: number): Observable> { - const mockConversations: ConversationDTO[] = []; - return of(new HttpResponse({ body: mockConversations })); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-course-exercise.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-course-exercise.service.ts deleted file mode 100644 index c30ba7a9a632..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-course-exercise.service.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { of } from 'rxjs'; -import { StudentParticipation } from 'app/exercise/shared/entities/participation/student-participation.model'; -import { ProgrammingExercise } from 'app/programming/shared/entities/programming-exercise.model'; - -export class MockCourseExerciseService { - startExercise = () => of({} as StudentParticipation); - - startPractice = () => of({} as StudentParticipation); - - resumeProgrammingExercise = () => of({} as StudentParticipation); - - findAllProgrammingExercisesForCourse = () => of([{ id: 456 } as ProgrammingExercise]); - - requestFeedback = () => of({}); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-course-management.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-course-management.service.ts deleted file mode 100644 index 15b0cfb28f12..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-course-management.service.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { HttpResponse } from '@angular/common/http'; -import { User, UserPublicInfoDTO } from 'app/core/user/user.model'; -import { BehaviorSubject, Observable, of } from 'rxjs'; -import { Course, CourseGroup } from 'app/core/course/shared/entities/course.model'; -import { TextExercise } from 'app/text/shared/entities/text-exercise.model'; -import { EntityArrayResponseType } from 'app/core/course/manage/services/course-management.service'; - -export class MockCourseManagementService { - find = (courseId: number) => of([{ id: 456 } as Course]); - - findWithExercises = (courseId: number) => { - const mockExercise = new TextExercise(undefined, undefined); - mockExercise.id = 1; - mockExercise.teamMode = true; - - const mockHttpBody = { - exercises: [mockExercise], - }; - - const mockHttpResponse = new HttpResponse({ body: mockHttpBody }); - - return of(mockHttpResponse); - }; - - coursesForNotificationsMock: BehaviorSubject = new BehaviorSubject(undefined); - getCoursesForNotifications = () => { - return this.coursesForNotificationsMock.asObservable(); - }; - - findAllCategoriesOfCourse = () => { - return of(); - }; - - getAllUsersInCourseGroup(courseId: number, courseGroup: CourseGroup): Observable> { - return of(new HttpResponse({ body: [] })); - } - - getAll(): Observable { - return of(new HttpResponse({ body: [] })); - } - - getNumberOfAllowedComplaintsInCourse(courseId: number): Observable { - return of(3); - } - - searchUsers(courseId: number, loginOrName: string, roles: string[]): Observable> { - const users: UserPublicInfoDTO[] = [ - { id: 1, name: 'Alice Johnson', imageUrl: 'alice.png' } as UserPublicInfoDTO, - { id: 2, name: 'Bob Smith', imageUrl: 'bob.png' } as UserPublicInfoDTO, - { id: 3, name: 'User1', imageUrl: 'user1.png' } as UserPublicInfoDTO, - ]; - - const filteredUsers = users.filter((user) => user.name!.toLowerCase().includes(loginOrName.toLowerCase()) || user.id!.toString() === loginOrName); - - return of(new HttpResponse({ body: filteredUsers })); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-course.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-course.service.ts deleted file mode 100644 index 396fa89c083c..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-course.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Course } from 'app/core/course/shared/entities/course.model'; -import { of } from 'rxjs'; -import { HttpResponse } from '@angular/common/http'; - -export class MockCourseService { - create = (course: Course) => of({} as HttpResponse); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-exam-checklist.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-exam-checklist.service.ts deleted file mode 100644 index 8ea30076b7b9..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-exam-checklist.service.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Exam } from 'app/exam/shared/entities/exam.model'; -import { ExamChecklist } from 'app/exam/shared/entities/exam-checklist.model'; -import { of } from 'rxjs'; - -export class MockExamChecklistService { - checkAtLeastOneExerciseGroup(exam: Exam) { - return true; - } - - checkNumberOfExerciseGroups(exam: Exam) { - return true; - } - - checkEachGroupContainsExercise(exam: Exam) { - return true; - } - - checkPointsExercisesEqual(exam: Exam) { - return true; - } - - checkTotalPointsMandatory(maximumPointsEqual: boolean, exam: Exam) { - return true; - } - - checkAtLeastOneRegisteredStudent(exam: Exam) { - return true; - } - - checkAllExamsGenerated(exam: Exam) { - return true; - } - - getExamStatistics(exam: Exam) { - const checklist = new ExamChecklist(); - checklist.allExamExercisesAllStudentsPrepared = true; - return of(checklist); - } - - calculateExercisePoints(pointsExercisesEqual: boolean, exam: Exam) { - return pointsExercisesEqual ? 10 : 0; - } - - getSubmittedTopic(exam: Exam) { - return `/topic/exam/${exam.id}/submitted`; - } - - getStartedTopic(exam: Exam) { - return `/topic/exam/${exam.id}/started`; - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-exam-exercise-update.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-exam-exercise-update.service.ts deleted file mode 100644 index e43d7abb37c9..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-exam-exercise-update.service.ts +++ /dev/null @@ -1,4 +0,0 @@ -export class MockExamExerciseUpdateService { - navigateToExamExercise = (exerciseId: number): void => {}; - updateLiveExamExercise = (exerciseId: number, problemStatement: string): void => {}; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-exam-management.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-exam-management.service.ts deleted file mode 100644 index a3bd4499bf0e..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-exam-management.service.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { HttpResponse } from '@angular/common/http'; -import { Exam } from 'app/exam/shared/entities/exam.model'; - -export class MockExamManagementService { - findAllCurrentAndUpcomingExams() { - return MockExamManagementService.response([{ id: 1 } as Exam, { id: 2 } as Exam]); - } - - // helper method - private static response(entity: T) { - return of({ body: entity }) as Observable>; - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-exam-participation-live-events.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-exam-participation-live-events.service.ts deleted file mode 100644 index 4544a791f757..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-exam-participation-live-events.service.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ExamLiveEvent, ExamLiveEventType } from 'app/exam/overview/services/exam-participation-live-events.service'; -import { Observable, of } from 'rxjs'; - -export class MockExamParticipationLiveEventsService { - public observeNewEventsAsSystem(eventTypes: ExamLiveEventType[] = []): Observable { - return of(); - } - - public observeNewEventsAsUser(eventTypes: ExamLiveEventType[] = []): Observable { - return of(); - } - - public observeAllEvents(eventTypes: ExamLiveEventType[] = []): Observable { - return of(); - } - - public acknowledgeEvent(event: ExamLiveEvent, byUser: boolean) {} -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-exam-participation.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-exam-participation.service.ts deleted file mode 100644 index c8d642eca261..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-exam-participation.service.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { StudentExam } from 'app/exam/shared/entities/student-exam.model'; -import { Exercise } from 'app/exercise/shared/entities/exercise/exercise.model'; -import { BehaviorSubject, of } from 'rxjs'; -import { Observable } from 'rxjs'; -import { StudentExamWithGradeDTO } from 'app/exam/manage/exam-scores/exam-score-dtos.model'; - -const studentExamInstance = new StudentExam(); -const exercise = { id: 7 }; -const exercises = [exercise]; -studentExamInstance.exercises = exercises as Exercise[]; - -const examParticipationSubjectMock = new BehaviorSubject(studentExamInstance); -const examEndViewSubject = new BehaviorSubject(false); - -export class MockExamParticipationService { - loadStudentExam = (courseId: number, examId: number): Observable => { - return examParticipationSubjectMock; - }; - loadStudentExamWithExercisesForSummary = (): Observable => { - return examParticipationSubjectMock; - }; - - loadStudentExamWithExercisesForConduction(courseId: number, examId: number): Observable { - return examParticipationSubjectMock; - } - - loadStudentExamWithExercisesForConductionOfTestExam(courseId: number, examId: number, studentExamId: number): Observable { - return examParticipationSubjectMock; - } - - loadStudentExamGradeInfoForSummary(courseId: number, examId: number, userId?: number, isTestRun?: boolean): Observable { - return of({} as StudentExamWithGradeDTO); - } - - getExamExerciseIds = (): number[] => { - return [1, 3, 7]; - }; - - saveStudentExamToLocalStorage(courseId: number, examId: number, studentExam: StudentExam): void {} - - public getOwnStudentExam(courseId: number, examId: number): Observable { - return of({} as StudentExam); - } - - setEndView(isEndView: boolean): void { - examEndViewSubject.next(false); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-exercise.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-exercise.service.ts deleted file mode 100644 index 4a8d7bc2b3f9..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-exercise.service.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { HttpResponse } from '@angular/common/http'; -import { Exercise } from 'app/exercise/shared/entities/exercise/exercise.model'; -import { EntityArrayResponseType, EntityResponseType } from 'app/exercise/services/exercise.service'; -import { convertDateFromClient } from 'app/shared/util/date.utils'; - -export class MockExerciseService { - find(exerciseId: number) { - return MockExerciseService.response({ id: exerciseId } as Exercise); - } - - getUpcomingExercises() { - return MockExerciseService.response([{ id: 1 } as Exercise, { id: 2 } as Exercise]); - } - - // helper method - private static response(entity: T) { - return of({ body: entity }) as Observable>; - } - - static convertExerciseFromClient(exercise: E): Exercise { - return exercise; - } - - static convertExerciseArrayDatesFromServer(res: EART): EART { - return res; - } - - static convertExerciseCategoryArrayFromServer(res: EART): EART { - return res; - } - - static convertExerciseResponseDatesFromServer(res: ERT): ERT { - return res; - } - - static convertExerciseDatesFromClient(exercise: E): E { - return Object.assign({}, exercise, { - releaseDate: convertDateFromClient(exercise.releaseDate), - dueDate: convertDateFromClient(exercise.dueDate), - assessmentDueDate: convertDateFromClient(exercise.assessmentDueDate), - }); - } - - static convertExerciseCategoriesFromServer(res: ERT): ERT { - return res; - } - - setBonusPointsConstrainedByIncludedInOverallScore(exercise: Exercise) { - return exercise; - } - - checkPermission(res: ERT): ERT { - return res; - } - - static stringifyExerciseCategories(exercise: Exercise) { - return exercise; - } - - processExerciseEntityResponse(exerciseRes: EntityResponseType): EntityResponseType { - return exerciseRes; - } - - processExerciseEntityArrayResponse(exerciseResArray: EntityArrayResponseType): EntityArrayResponseType { - return exerciseResArray; - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-feature-toggle.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-feature-toggle.service.ts deleted file mode 100644 index 7570c12277e4..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-feature-toggle.service.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ActiveFeatureToggles, FeatureToggle } from 'app/shared/feature-toggle/feature-toggle.service'; -import { BehaviorSubject, of } from 'rxjs'; -import { distinctUntilChanged, map } from 'rxjs/operators'; - -export class MockFeatureToggleService { - private subject: BehaviorSubject; - - subscribeFeatureToggleUpdates() {} - - unsubscribeFeatureToggleUpdates() {} - - getFeatureToggles() { - const defaultActiveFeatureState: ActiveFeatureToggles = Object.values(FeatureToggle); - this.subject = new BehaviorSubject(defaultActiveFeatureState); - - return this.subject; - } - - getFeatureToggleActive(feature: FeatureToggle) { - return this.subject.asObservable().pipe( - map((activeFeatures) => activeFeatures.includes(feature)), - distinctUntilChanged(), - ); - } - - setFeatureToggleState(featureToggle: FeatureToggle, active: boolean) { - const activeFeatureToggles = Object.values(FeatureToggle); - - if (!active) { - const index = activeFeatureToggles.indexOf(featureToggle); - if (index > -1) { - activeFeatureToggles.splice(index, 1); - } - } - - return of(this.subject.next(activeFeatureToggles)); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-file-upload-exercise.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-file-upload-exercise.service.ts deleted file mode 100644 index b5eab27dd426..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-file-upload-exercise.service.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { of } from 'rxjs'; -import { FileUploadExercise } from 'app/fileupload/shared/entities/file-upload-exercise.model'; -import { HttpResponse } from '@angular/common/http'; -import { Course } from 'app/core/course/shared/entities/course.model'; - -export const fileUploadExercise = new FileUploadExercise(undefined, undefined); -fileUploadExercise.id = 2; -fileUploadExercise.title = 'some title'; -fileUploadExercise.maxPoints = 20; -fileUploadExercise.filePattern = 'pdf,png'; -fileUploadExercise.problemStatement = 'Example problem statement'; -fileUploadExercise.course = new Course(); -fileUploadExercise.categories = []; - -export class MockFileUploadExerciseService { - create = (fileUploadExerciseParam: FileUploadExercise) => of(); - update = (fileUploadExerciseParam: FileUploadExercise, exerciseId: number, req?: any) => of(); - find = (fileUploadExerciseId: number) => of(new HttpResponse({ body: fileUploadExercise })); - query = (req?: any) => of(); - delete = (fileUploadExerciseId: number) => of(); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-file-upload-submission.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-file-upload-submission.service.ts deleted file mode 100644 index 88615206df0c..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-file-upload-submission.service.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { of } from 'rxjs'; -import { fileUploadExercise } from './mock-file-upload-exercise.service'; -import { Result } from 'app/exercise/shared/entities/result/result.model'; -import { FileUploadSubmission } from 'app/fileupload/shared/entities/file-upload-submission.model'; -import { StudentParticipation } from 'app/exercise/shared/entities/participation/student-participation.model'; - -export const fileUploadParticipation = new StudentParticipation(); -fileUploadParticipation.exercise = fileUploadExercise; -fileUploadParticipation.id = 1; - -export const createFileUploadSubmission = () => { - const fileUploadSubmission = new FileUploadSubmission(); - fileUploadSubmission.submitted = false; - fileUploadSubmission.participation = fileUploadParticipation; - fileUploadSubmission.id = 1; - return fileUploadSubmission; -}; - -export class MockFileUploadSubmissionService { - getDataForFileUploadEditor = (participationId: number) => of(createFileUploadSubmission()); - update = (fileUploadSubmission: FileUploadSubmission, exerciseId: number) => { - fileUploadSubmission.results = [new Result()]; - return of({ body: fileUploadSubmission }); - }; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-file.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-file.service.ts deleted file mode 100644 index 24633867198e..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-file.service.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { of } from 'rxjs'; - -export class MockFileService { - downloadMergedFile = () => { - return of({ body: null }); - }; - - downloadFile = () => { - return { subscribe: (fn: (value: any) => void) => fn({ body: new Window() }) }; - }; - downloadFileByAttachmentName = () => { - return { subscribe: (fn: (value: any) => void) => fn({ body: new Window() }) }; - }; - - getTemplateFile = () => { - return of(); - }; - - createAttachmentFileUrl(downloadUrl: string, downloadName: string, encodeName: boolean) { - return 'attachments/' + downloadName.replace(' ', '-') + '.pdf'; - } - - replaceLectureAttachmentPrefixAndUnderscores = (link: string) => link; - replaceAttachmentPrefixAndUnderscores = (link: string) => link; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-forwarded-message.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-forwarded-message.service.ts deleted file mode 100644 index 1e31b0db3273..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-forwarded-message.service.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { HttpResponse } from '@angular/common/http'; -import { ForwardedMessage } from 'app/communication/shared/entities/forwarded-message.model'; -import { PostingType } from 'app/communication/shared/entities/posting.model'; - -export class MockForwardedMessageService { - private forwardedMessages: Map = new Map(); - - createForwardedMessage(forwardedMessage: ForwardedMessage): Observable> { - const destinationPostId = forwardedMessage.destinationPost?.id!; - const messagesForPost = this.forwardedMessages.get(destinationPostId) || []; - messagesForPost.push(forwardedMessage); - this.forwardedMessages.set(destinationPostId, messagesForPost); - - return of(new HttpResponse({ body: forwardedMessage })); - } - - getForwardedMessages(postIds: number[], type: PostingType): Observable>> { - const result = new Map(); - postIds.forEach((postId) => { - if (this.forwardedMessages.has(postId)) { - result.set(postId, this.forwardedMessages.get(postId)!); - } - }); - - return of(new HttpResponse({ body: result })); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-http.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-http.service.ts deleted file mode 100644 index c34cade6537f..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-http.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { of } from 'rxjs'; - -export class MockHttpService { - get = (url: string) => of(); - post = () => of(); - put = () => of(); - patch = () => of(); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-ide-build-and-test.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-ide-build-and-test.service.ts deleted file mode 100644 index 4ed8d89cf9e9..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-ide-build-and-test.service.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { of } from 'rxjs'; -import { ProgrammingExercise } from 'app/programming/shared/entities/programming-exercise.model'; - -export class MockIdeBuildAndTestService { - listenOnBuildOutputAndForwardChanges = (exercise: ProgrammingExercise) => of(); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-link-preview.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-link-preview.service.ts deleted file mode 100644 index 6a6577e7cb77..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-link-preview.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { LinkPreview } from 'app/communication/link-preview/services/link-preview.service'; - -export class MockLinkPreviewService { - fetchLink(url: string): Observable { - return of({} as LinkPreview); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-linkify.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-linkify.service.ts deleted file mode 100644 index 36c2d4c91273..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-linkify.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Link } from 'app/communication/link-preview/services/linkify.service'; - -export class MockLinkifyService { - find(text: string): Link[] { - return []; - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-local-storage.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-local-storage.service.ts deleted file mode 100644 index 34f2a954fa6c..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-local-storage.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable({ providedIn: 'root' }) -export class MockLocalStorageService { - storage: { [key: string]: any } = {}; - store = (key: string, value: string) => (this.storage[key] = value); - retrieve = (key: string) => this.storage[key]; - clear = (key?: string) => {}; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-lti-configuration-service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-lti-configuration-service.ts deleted file mode 100644 index b1b946e3e38d..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-lti-configuration-service.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { LtiPlatformConfiguration } from 'app/lti/shared/entities/lti-configuration.model'; -import { HttpResponse } from '@angular/common/http'; - -export class MockLtiConfigurationService { - private dummyLtiPlatforms: LtiPlatformConfiguration[] = [ - { - id: 1, - registrationId: 'platform-1', - customName: 'Platform A', - originalUrl: 'lms1.com', - clientId: 'client-id-a', - authorizationUri: 'lms1.com/aut-callback', - tokenUri: 'lms1.com/token', - jwkSetUri: 'lms1.com/jwk', - }, - { - id: 2, - registrationId: 'platform-2', - customName: 'Platform B', - originalUrl: 'lms2.com', - clientId: 'client-id-b', - authorizationUri: 'lms2.com/aut-callback', - tokenUri: 'lms2.com/token', - jwkSetUri: 'lms2.com/jwk', - }, - ]; - - public query(req: any): Observable> { - return of( - new HttpResponse({ - body: this.dummyLtiPlatforms, - status: 200, // Assuming a successful response - }), - ); - } - - public updateLtiPlatformConfiguration(config: LtiPlatformConfiguration) { - const index = this.dummyLtiPlatforms.findIndex((p) => p.id === config.id); - if (index !== -1) { - this.dummyLtiPlatforms[index] = config; - } - return of({ status: 200, statusText: 'OK' }); - } - - public deleteLtiPlatform(platformId: number) { - this.dummyLtiPlatforms = this.dummyLtiPlatforms.filter((p) => p.id !== platformId); - return of({ status: 200, statusText: 'Deleted' }); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-metis-conversation.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-metis-conversation.service.ts deleted file mode 100644 index c0172b2c0d3d..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-metis-conversation.service.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Course } from 'app/core/course/shared/entities/course.model'; -import { BehaviorSubject, EMPTY, Observable } from 'rxjs'; -import { ConversationDTO } from 'app/communication/shared/entities/conversation/conversation.model'; -import { GroupChatDTO } from 'app/communication/shared/entities/conversation/group-chat.model'; - -export class MockMetisConversationService { - get course(): Course | undefined { - return undefined; - } - - get activeConversation$(): Observable { - return EMPTY; - } - - get isServiceSetup$(): Observable { - return new BehaviorSubject(true).asObservable(); - } - - get conversationsOfUser$(): Observable { - return new BehaviorSubject([new GroupChatDTO()]).asObservable(); - } - - get isLoading$(): Observable { - return new BehaviorSubject(false).asObservable(); - } - - get isCodeOfConductAccepted$(): Observable { - return new BehaviorSubject(true).asObservable(); - } - - get isCodeOfConductPresented$(): Observable { - return new BehaviorSubject(false).asObservable(); - } - - checkIsCodeOfConductAccepted(): void {} - - setActiveConversation(conversationIdentifier: ConversationDTO | number | undefined) {} - - setUpConversationService = (course: Course): Observable => { - return EMPTY; - }; - - createOneToOneChatWithId = (userId: number): Observable => { - return EMPTY; - }; - - createOneToOneChat = (userId: number): Observable => { - return EMPTY; - }; - - forceRefresh(notifyActiveConversationSubscribers = true, notifyConversationsSubscribers = true): Observable { - return EMPTY; - } - - markAsRead(): void {} - - acceptCodeOfConduct(course: Course) {} -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-metis-service.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-metis-service.service.ts deleted file mode 100644 index 31c9f0b61873..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-metis-service.service.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { AnswerPost } from 'app/communication/shared/entities/answer-post.model'; -import { Post } from 'app/communication/shared/entities/post.model'; -import { Posting } from 'app/communication/shared/entities/posting.model'; -import { User } from 'app/core/user/user.model'; -import { Reaction } from 'app/communication/shared/entities/reaction.model'; -import { ContextInformation, PageType, PostContextFilter, RouteComponents } from 'app/communication/metis.util'; -import { Course } from 'app/core/course/shared/entities/course.model'; -import { Params } from '@angular/router'; -import { metisCourse, metisCoursePosts, metisTags, metisUser1 } from '../../sample/metis-sample-data'; -import { ChannelDTO, ChannelSubType, getAsChannelDTO } from 'app/communication/shared/entities/conversation/channel.model'; -import { ConversationDTO } from 'app/communication/shared/entities/conversation/conversation.model'; - -let pageType: PageType; - -export class MockMetisService { - currentConversation = undefined; - - get tags(): Observable { - return of(metisTags); - } - - get posts(): Observable { - return of(metisCoursePosts); - } - - getUser(): User { - return metisUser1; - } - - getCourse(): Course { - return metisCourse; - } - - getPageType(): PageType { - return pageType; - } - - setPageType(newPageType: PageType) { - pageType = newPageType; - } - - getCurrentConversation(): ConversationDTO | undefined { - return this.currentConversation; - } - - createPost = (post: Post): Observable => of(post); - - createAnswerPost = (answerPost: AnswerPost): Observable => of({ ...answerPost }); - - createReaction = (reaction: Reaction): Observable => of(reaction); - - updatePost = (post: Post): Observable => of(post); - - updateAnswerPost = (answerPost: AnswerPost): Observable => of(answerPost); - - deletePost(post: Post): void {} - - deleteAnswerPost(answerPost: AnswerPost): void {} - - deleteReaction(reaction: Reaction): void {} - - resetCachedPosts(): void {} - - metisUserIsAtLeastTutorInCourse(): boolean { - return true; - } - - metisUserIsAtLeastInstructorInCourse(): boolean { - return true; - } - - metisUserIsAuthorOfPosting(posting: Posting): boolean { - return true; - } - - getFilteredPosts(postContextFilter: PostContextFilter, forceUpdate = true): void {} - - isPostResolved(post: Post) { - return false; - } - - getLinkForPost(post?: Post): RouteComponents { - return ['/courses', metisCourse.id!, 'discussion']; - } - - getLinkForExercise(exerciseId: string): string { - return `/courses/${metisCourse.id}/exercises/${exerciseId}`; - } - - getLinkForLecture(lectureId: string): string { - return '/courses/' + metisCourse.id + '/lectures/' + lectureId; - } - - getLinkForExam(examId: string): string { - return '/courses/' + metisCourse.id + '/exams/' + examId; - } - - getLinkForFaq(): string { - return `/courses/${this.getCourse().id}/faq`; - } - - getLinkForChannelSubType(channel?: ChannelDTO): string | undefined { - const referenceId = channel?.subTypeReferenceId?.toString(); - if (!referenceId) { - return undefined; - } - - switch (channel?.subType) { - case ChannelSubType.EXERCISE: - return this.getLinkForExercise(referenceId); - case ChannelSubType.LECTURE: - return this.getLinkForLecture(referenceId); - case ChannelSubType.EXAM: - return this.getLinkForExam(referenceId); - default: - return undefined; - } - } - - getContextInformation(post: Post): ContextInformation { - const routerLinkComponents = ['/courses', post.conversation?.course?.id ?? 1, 'communication']; - const queryParams = { conversationId: post.conversation?.id }; - const displayName = getAsChannelDTO(post.conversation)?.name ?? 'some context'; - return { routerLinkComponents, displayName, queryParams }; - } - - getQueryParamsForPost(post: Post): Params { - const params: Params = {}; - params.searchText = `#${post.id}`; - return params; - } - - getSimilarPosts(title: string): Observable { - return of(metisCoursePosts.slice(0, 5)); - } - - setCourse(course: Course | undefined): void {} -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-ngb-active-modal.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-ngb-active-modal.service.ts deleted file mode 100644 index d164d07609bc..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-ngb-active-modal.service.ts +++ /dev/null @@ -1,4 +0,0 @@ -export class MockNgbActiveModalService { - close = (value: any) => true; - dismiss = () => true; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-ngb-modal.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-ngb-modal.service.ts deleted file mode 100644 index cd90e74f1e47..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-ngb-modal.service.ts +++ /dev/null @@ -1,4 +0,0 @@ -export class MockNgbModalService { - open = (component: any, options: any) => ({ componentInstance: {}, result: { then: () => undefined }, close: () => {} }); - hasOpenModals = () => false; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-notification-settings.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-notification-settings.service.ts deleted file mode 100644 index cf5032956a57..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-notification-settings.service.ts +++ /dev/null @@ -1,4 +0,0 @@ -export class MockNotificationSettingsService { - createUpdatedNotificationTitleActivationMap = () => {}; - isNotificationAllowedBySettings = () => {}; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-notification.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-notification.service.ts deleted file mode 100644 index b9fcb93e962b..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-notification.service.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { BehaviorSubject, Observable, of } from 'rxjs'; -import { HttpResponse } from '@angular/common/http'; -import { Notification } from 'app/core/shared/entities/notification.model'; -import { GroupNotification } from 'app/core/shared/entities/group-notification.model'; -import { MetisPostDTO } from 'app/communication/shared/entities/metis-post-dto.model'; - -export class MockNotificationService { - queryNotificationsFilteredBySettings = (req?: any): Observable> => of(); - subscribeToNotificationUpdates = (): BehaviorSubject => new BehaviorSubject(null); - interpretNotification = (notification: GroupNotification): void => {}; - cleanUp = () => {}; - forceComponentReload = () => {}; - - get newOrUpdatedMessage(): Observable { - return of(); - } - - subscribeToTotalNotificationCountUpdates = (): Observable => of(); - - getNotificationTextTranslation = (notification: Notification, maxContentLength: number) => { - if (notification.textIsPlaceholder) { - return notification.text; - } - return notification.text ?? 'No text found'; - }; - - subscribeToLoadingStateUpdates = (): Observable => of(); - - subscribeToSingleIncomingNotifications = (): Observable => of(); - - handleNotification(postDTO: MetisPostDTO) {} - - public muteNotificationsForConversation(conversationId: number) {} - - public unmuteNotificationsForConversation(conversationId: number) {} -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-participation-websocket.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-participation-websocket.service.ts deleted file mode 100644 index 8b67b15cff4d..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-participation-websocket.service.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { BehaviorSubject } from 'rxjs'; -import { Participation } from 'app/exercise/shared/entities/participation/participation.model'; -import { IParticipationWebsocketService } from 'app/core/course/shared/services/participation-websocket.service'; -import { Exercise } from 'app/exercise/shared/entities/exercise/exercise.model'; -import { Result } from 'app/exercise/shared/entities/result/result.model'; - -export class MockParticipationWebsocketService implements IParticipationWebsocketService { - addParticipation = (participation: Participation, exercise?: Exercise) => {}; - getParticipationsForExercise = (exerciseId: number) => undefined; - subscribeForParticipationChanges = () => new BehaviorSubject(undefined); - subscribeForLatestResultOfParticipation = (participationId: number) => new BehaviorSubject(undefined); - unsubscribeForLatestResultOfParticipation = (participationId: number, exercise: Exercise) => {}; - notifyAllResultSubscribers = (result: Result) => {}; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-participation.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-participation.service.ts deleted file mode 100644 index a16c430d4dc9..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-participation.service.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Participation } from 'app/exercise/shared/entities/participation/participation.model'; -import { EntityArrayResponseType } from 'app/exercise/participation/participation.service'; -import { EMPTY, Observable, of } from 'rxjs'; -import { StudentParticipation } from 'app/exercise/shared/entities/participation/student-participation.model'; - -export class MockParticipationService { - findWithLatestResult = (participationId: number) => of({} as Participation); - mergeStudentParticipations = (participations: StudentParticipation[]) => participations; - getSpecificStudentParticipation = (studentParticipations: StudentParticipation[], testRun: boolean) => - studentParticipations.filter((participation) => !!participation.testRun === testRun).first(); - - findAllParticipationsByExercise = (exerciseId: number, withLatestResults = false): Observable => EMPTY; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-post.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-post.service.ts deleted file mode 100644 index b20c30737dfa..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-post.service.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { Post } from 'app/communication/shared/entities/post.model'; -import { HttpHeaders, HttpResponse } from '@angular/common/http'; -import { DisplayPriority, PostContextFilter } from 'app/communication/metis.util'; -import { messagesBetweenUser1User2, metisCoursePosts, metisPostExerciseUser1, metisPostInChannel, metisTags } from '../../sample/metis-sample-data'; - -export class MockPostService { - create(courseId: number, post: Post): Observable> { - return of({ body: post }) as Observable>; - } - - update(courseId: number, post: Post): Observable> { - return of({ body: post }) as Observable>; - } - - updatePostDisplayPriority(courseId: number, postId: number, displayPriority: DisplayPriority): Observable> { - return of({ body: { id: postId, displayPriority } as Post }) as Observable>; - } - - delete(post: Post): Observable> { - return of({ body: {} }) as Observable>; - } - - getPosts(courseId: number, postContextFilter: PostContextFilter): Observable> { - if (postContextFilter.conversationId) { - return of({ - body: messagesBetweenUser1User2, - headers: new HttpHeaders({ - 'X-Total-Count': messagesBetweenUser1User2.length.toString(), - }), - }) as Observable>; - } - if (postContextFilter.courseWideChannelIds) { - return of({ - body: [metisPostInChannel], - headers: new HttpHeaders({ - 'X-Total-Count': 1, - }), - }) as Observable>; - } else { - return of({ - body: !postContextFilter.pageSize ? metisCoursePosts : metisCoursePosts.slice(0, postContextFilter.pageSize), - headers: new HttpHeaders({ - 'X-Total-Count': metisCoursePosts.length.toString(), - }), - }) as Observable>; - } - } - - getAllPostTagsByCourseId(courseId: number): Observable> { - return of({ body: metisTags }) as Observable>; - } - - computeSimilarityScoresWithCoursePosts(post: Post, courseId: number): Observable> { - return of({ body: [metisPostExerciseUser1] }) as Observable>; - } - - getSourcePostsByIds(courseId: number, postIds: number[]): Observable { - const sourcePosts = metisCoursePosts.filter((post) => postIds.includes(post.id!)); - return of(sourcePosts); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-profile.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-profile.service.ts deleted file mode 100644 index 34cca181a1d7..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-profile.service.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BehaviorSubject } from 'rxjs'; -import { ProfileInfo } from 'app/core/layouts/profiles/profile-info.model'; - -export class MockProfileService { - getProfileInfo = () => - new BehaviorSubject({ - activeProfiles: [], - activeModuleFeatures: [], - } as unknown as ProfileInfo); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-programming-build-run.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-programming-build-run.service.ts deleted file mode 100644 index 3f3e306b274b..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-programming-build-run.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { BuildRunState, IProgrammingBuildRunService } from 'app/programming/shared/services/programming-build-run.service'; -import { Observable, of } from 'rxjs'; - -export class MockProgrammingBuildRunService implements IProgrammingBuildRunService { - getBuildRunUpdates(programmingExerciseId: number): Observable { - return of(BuildRunState.COMPLETED); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise-grading.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise-grading.service.ts deleted file mode 100644 index 044e4c59228e..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise-grading.service.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { BehaviorSubject, Observable, of } from 'rxjs'; -import { ProgrammingExerciseTestCase } from 'app/programming/shared/entities/programming-exercise-test-case.model'; -import { - IProgrammingExerciseGradingService, - ProgrammingExerciseTestCaseUpdate, - StaticCodeAnalysisCategoryUpdate, -} from 'app/programming/manage/services/programming-exercise-grading.service'; -import { StaticCodeAnalysisCategory } from 'app/programming/shared/entities/static-code-analysis-category.model'; -import { ProgrammingExerciseGradingStatistics } from 'app/programming/shared/entities/programming-exercise-test-case-statistics.model'; - -export class MockProgrammingExerciseGradingService implements IProgrammingExerciseGradingService { - private testCaseSubject = new BehaviorSubject(undefined); - private categorySubject = new BehaviorSubject(undefined); - - subscribeForTestCases(exerciseId: number): Observable { - return this.testCaseSubject as Observable; - } - - initSubject(initialValue: ProgrammingExerciseTestCase[]) { - if (this.testCaseSubject) { - this.testCaseSubject.complete(); - } - this.testCaseSubject = new BehaviorSubject(initialValue); - } - - nextTestCases(value: ProgrammingExerciseTestCase[] | undefined) { - this.testCaseSubject.next(value); - } - - notifyTestCases(exerciseId: number, testCases: ProgrammingExerciseTestCase[]): void { - this.testCaseSubject.next(testCases); - } - - resetTestCases(exerciseId: number): Observable { - return of(); - } - - resetCategories(exerciseId: number): Observable { - return of(); - } - - updateTestCase(exerciseId: number, updates: ProgrammingExerciseTestCaseUpdate[]): Observable { - return of(); - } - - getCodeAnalysisCategories(exerciseId: number): Observable { - return this.categorySubject as Observable; - } - - updateCodeAnalysisCategories(exerciseId: number, updates: StaticCodeAnalysisCategoryUpdate[]): Observable { - return of(); - } - - nextCategories(value: StaticCodeAnalysisCategory[]) { - this.categorySubject.next(value); - } - - getGradingStatistics(exerciseId: number): Observable { - return of(); - } - - importCategoriesFromExercise(targetExerciseId: number, sourceExerciseId: number): Observable { - return of(); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise-participation.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise-participation.service.ts deleted file mode 100644 index 995ee939db7e..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise-participation.service.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { of } from 'rxjs'; -import { IProgrammingExerciseParticipationService } from 'app/programming/manage/services/programming-exercise-participation.service'; -import { ProgrammingExerciseStudentParticipation } from 'app/exercise/shared/entities/participation/programming-exercise-student-participation.model'; -import { Result } from 'app/exercise/shared/entities/result/result.model'; -import { CommitInfo } from 'app/programming/shared/entities/programming-submission.model'; -import { VcsAccessLogDTO } from 'app/programming/shared/entities/vcs-access-log-entry.model'; - -export class MockProgrammingExerciseParticipationService implements IProgrammingExerciseParticipationService { - getLatestResultWithFeedback = (participationId: number, withSubmission: boolean) => of({} as Result); - getStudentParticipationWithLatestResult = (participationId: number) => of({} as ProgrammingExerciseStudentParticipation); - getStudentParticipationWithAllResults = (participationId: number) => of({} as ProgrammingExerciseStudentParticipation); - retrieveCommitHistoryForParticipation = (participationId: number) => of([] as CommitInfo[]); - retrieveCommitHistoryForTemplateSolutionOrTests = (participationId: number, repositoryType: string) => of([] as CommitInfo[]); - retrieveCommitHistoryForAuxiliaryRepository = (exerciseId: number, repositoryId: number) => of([] as CommitInfo[]); - getParticipationRepositoryFilesWithContentAtCommitForCommitDetailsView = (exerciseId: number, participationId: number, commitId: string, repositoryType: string) => - of(new Map()); - checkIfParticipationHasResult = (participationId: number) => of(true); - getVcsAccessLogForRepository = (exerciseId: number, repositoryType: string) => of([] as VcsAccessLogDTO[]); - getVcsAccessLogForParticipation = (participationId: number) => of([] as VcsAccessLogDTO[]); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise-websocket.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise-websocket.service.ts deleted file mode 100644 index 28db650d4742..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise-websocket.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IProgrammingExerciseWebsocketService } from 'app/programming/manage/services/programming-exercise-websocket.service'; -import { Observable, of } from 'rxjs'; - -export class MockProgrammingExerciseWebsocketService implements IProgrammingExerciseWebsocketService { - getTestCaseState(programmingExerciseId: number): Observable { - return of(false); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise.service.ts deleted file mode 100644 index 8ba7ab831067..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise.service.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { of } from 'rxjs'; -import { Participation } from 'app/exercise/shared/entities/participation/participation.model'; -import { ProgrammingLanguage } from 'app/programming/shared/entities/programming-exercise.model'; -import { RepositoryType } from '../../../../../../main/webapp/app/programming/shared/code-editor/model/code-editor.model'; - -export class MockProgrammingExerciseService { - updateProblemStatement = (exerciseId: number, problemStatement: string) => of(); - findWithTemplateAndSolutionParticipation = (exerciseId: number) => of(); - findWithTemplateAndSolutionParticipationAndResults = (exerciseId: number) => of(); - findWithTemplateAndSolutionParticipationAndLatestResults = (exerciseId: number) => of(); - findWithAuxiliaryRepository = (programmingExerciseId: number) => of(); - find = (exerciseId: number) => of({ body: { id: 4 } }); - getProgrammingExerciseTestCaseState = (exerciseId: number) => of({ body: { released: true, hasStudentResult: true, testCasesChanged: false } }); - exportInstructorExercise = (exerciseId: number) => of({ body: undefined }); - exportInstructorRepository = (exerciseId: number, repositoryType: RepositoryType) => of({ body: undefined }); - exportStudentRepository = (exerciseId: number, participationId: number) => of({ body: undefined }); - exportStudentRequestedRepository = (exerciseId: number, includeTests: boolean) => of({ body: undefined }); - getDiffReport = (exerciseId: number) => of({}); - getTheiaConfig = (exerciseId: number) => of({}); - createStructuralSolutionEntries = (exerciseId: number) => of({}); - createBehavioralSolutionEntries = (exerciseId: number) => of({}); - getLatestResult = (participation: Participation) => of({}); - combineTemplateRepositoryCommits = (exerciseId: number) => of({}); - delete = (programmingExerciseId: number, deleteStudentReposBuildPlans: boolean, deleteBaseReposBuildPlans: boolean) => of({}); - generateStructureOracle = (exerciseId: number) => of({}); - getDiffReportForCommits = (exerciseId: number, participationId: number, olderCommitHash: string, newerCommitHash: string, repositoryType: string) => of({}); - getCheckoutDirectoriesForProgrammingLanguage = (programmingLanguage: ProgrammingLanguage, checkoutSolution: boolean) => of(); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-programming-exericse-instruction-analysis.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-programming-exericse-instruction-analysis.service.ts deleted file mode 100644 index 041a5766eef6..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-programming-exericse-instruction-analysis.service.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class MockProgrammingExerciseInstructionAnalysisService { - public analyzeProblemStatement = (problemStatement: string, taskRegex: RegExp, exerciseTestCases: string[]) => { - return {}; - }; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-programming-submission.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-programming-submission.service.ts deleted file mode 100644 index 437531781894..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-programming-submission.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { IProgrammingSubmissionService, ProgrammingSubmissionState, ProgrammingSubmissionStateObj } from 'app/programming/shared/services/programming-submission.service'; -import { EMPTY, Observable, of } from 'rxjs'; -import dayjs from 'dayjs/esm'; -import { Exercise } from 'app/exercise/shared/entities/exercise/exercise.model'; - -export class MockProgrammingSubmissionService implements IProgrammingSubmissionService { - getLatestPendingSubmissionByParticipationId = (participationId: number) => - of([1, ProgrammingSubmissionState.HAS_NO_PENDING_SUBMISSION, null] as unknown as ProgrammingSubmissionStateObj); - getSubmissionStateOfExercise = (exerciseId: number) => EMPTY; - triggerBuild = (participationId: number) => of({}); - triggerInstructorBuild = (participationId: number) => of({}); - unsubscribeAllWebsocketTopics = (exercise: Exercise) => of(null); - unsubscribeForLatestSubmissionOfParticipation = (participationId: number) => of(null); - getResultEtaInMs: () => Observable; - triggerInstructorBuildForAllParticipationsOfExercise: (exerciseId: number) => Observable; - triggerInstructorBuildForParticipationsOfExercise: (exerciseId: number, participationIds: number[]) => Observable; - getIsLocalCIProfile = () => false; - fetchQueueReleaseDateEstimationByParticipationId: (participationId: number) => Observable = () => of(undefined); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-rating.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-rating.service.ts deleted file mode 100644 index 0b04fde83171..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-rating.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Rating } from 'app/assessment/shared/entities/rating.model'; -import { Observable, of } from 'rxjs'; - -export class MockRatingService { - createRating = (rating: Rating): Observable => of(rating); - updateRating = (rating: Rating): Observable => of(rating); - getRating = (ratingId: number): Observable => of(null); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-reaction.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-reaction.service.ts deleted file mode 100644 index 0b960b8be079..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-reaction.service.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { HttpResponse } from '@angular/common/http'; -import { Reaction } from 'app/communication/shared/entities/reaction.model'; - -export class MockReactionService { - create(courseId: number, reaction: Reaction): Observable> { - return of({ body: reaction }) as Observable>; - } - - delete(courseId: number, reaction: Reaction): Observable> { - return of({ body: {} }) as Observable>; - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-repository-file.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-repository-file.service.ts deleted file mode 100644 index 563c0becf353..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-repository-file.service.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { EMPTY, of } from 'rxjs'; -import { IRepositoryFileService } from 'app/programming/shared/services/repository.service'; - -export class MockRepositoryFileService implements IRepositoryFileService { - createFile = (participationId: number, fileName: string) => EMPTY; - createFolder = (participationId: number, folderName: string) => EMPTY; - delete = (participationId: number, fileName: string) => EMPTY; - get = (participationId: number, fileName: string) => of(); - query = (participationId: number) => of({}); - rename = (participationId: number, currentFilePath: string, newFilename: string) => EMPTY; - update = (participationId: number, fileName: string, fileContent: string) => of(); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-resize-observer.ts b/src/test/javascript/spec/helpers/mocks/service/mock-resize-observer.ts deleted file mode 100644 index 935dfb56800f..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-resize-observer.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Mock class for the global ResizeObserver class. - * https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver - */ -export class MockResizeObserver { - constructor(callback: ResizeObserverCallback) { - // Do nothing - } - - observe(element: Element): void { - // Do nothing - } - - unobserve(element: Element): void { - // Do nothing - } - - disconnect(): void { - // Do nothing - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-result.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-result.service.ts deleted file mode 100644 index fa2599070725..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-result.service.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { EMPTY, Observable } from 'rxjs'; -import { IResultService, ResultsWithPointsArrayResponseType } from 'app/exercise/result/result.service'; -import { Result } from 'app/exercise/shared/entities/result/result.model'; -import { Exercise } from 'app/exercise/shared/entities/exercise/exercise.model'; - -export class MockResultService implements IResultService { - find = (resultId: number) => EMPTY; - getFeedbackDetailsForResult = (participationId: number, result: Result) => EMPTY; - getResultsForExerciseWithPointsPerGradingCriterion = (exerciseId: number, req: any) => EMPTY; - getResultsWithPointsPerGradingCriterion = (exercise: Exercise): Observable => EMPTY; - triggerDownloadCSV = (rows: string[], csvFileName: string) => EMPTY; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-science-service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-science-service.ts deleted file mode 100644 index 33c295ae9df5..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-science-service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { HttpResponse } from '@angular/common/http'; - -export class MockScienceService { - eventLoggingActive = () => true; - logEvent = (type: string, resourceId?: number) => of({}) as Observable>; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-server-date.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-server-date.service.ts deleted file mode 100644 index c4006357ed2c..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-server-date.service.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ServerDateService } from 'app/shared/service/server-date.service'; -import dayjs from 'dayjs/esm'; -import { HttpClient } from '@angular/common/http'; - -export class MockArtemisServerDateService implements ServerDateService { - recentClientDates: Array; - recentOffsets: Array; - resourceUrl: string; - http: HttpClient; - - now(): dayjs.Dayjs { - return dayjs(); - } - - setServerDate(date: string): void {} - - updateTime(): void {} -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-ssh-user-settings.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-ssh-user-settings.service.ts deleted file mode 100644 index cfa27a742c6d..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-ssh-user-settings.service.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { of } from 'rxjs'; -import { UserSshPublicKey } from 'app/programming/shared/entities/user-ssh-public-key.model'; -import { IASshUserSettingsService } from 'app/core/user/settings/ssh-settings/ssh-user-settings.service'; -export class MockSshUserSettingsService implements IASshUserSettingsService { - getCachedSshKeys = () => Promise.resolve([]); - getSshPublicKeys = () => of([]); - getSshPublicKey = (keyId: number) => of({ id: 99 } as UserSshPublicKey); - addNewSshPublicKey = (userKey: UserSshPublicKey) => of(); - deleteSshPublicKey = (keyId: number) => of(); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-sync-storage.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-sync-storage.service.ts deleted file mode 100644 index a80f213406a9..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-sync-storage.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { StorageService } from 'ngx-webstorage/lib/core/interfaces/storageService'; -import { Observable } from 'rxjs'; - -export class MockSyncStorage implements StorageService { - private static storage: { [key: string]: any } = {}; - - clear(key?: string): any {} - - getStrategyName(): string { - return ''; - } - - observe(key: string): Observable { - // @ts-ignore - return undefined; - } - - retrieve(key: string): any {} - - store(key: string, value: any): any { - MockSyncStorage.storage[key] = `${value}`; - } - - static retrieve(key: string): any { - return this.storage[key]; - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-team.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-team.service.ts deleted file mode 100644 index d872dbd053f0..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-team.service.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { HttpResponse } from '@angular/common/http'; -import { ITeamService } from 'app/exercise/team/team.service'; -import { Exercise } from 'app/exercise/shared/entities/exercise/exercise.model'; -import { Team, TeamImportStrategyType } from 'app/exercise/shared/entities/team/team.model'; -import { StudentWithTeam } from 'app/exercise/shared/entities/team/team.model'; -import { Course } from 'app/core/course/shared/entities/course.model'; -import { TeamSearchUser } from 'app/exercise/shared/entities/team/team-search-user.model'; -import { User } from 'app/core/user/user.model'; -import { ProgrammingExercise } from 'app/programming/shared/entities/programming-exercise.model'; -import { TeamAssignmentConfig } from 'app/exercise/shared/entities/team/team-assignment-config.model'; -import dayjs from 'dayjs/esm'; - -export const mockTeamStudents = [ - { id: 1, firstName: 'John', lastName: 'Doe', name: 'John Doe', login: 'ga12abc', email: 'john.doe@example.com', visibleRegistrationNumber: '01234567' }, - { id: 2, firstName: 'Sidney', lastName: 'Humphrey', name: 'Sidney Humphrey', login: 'ga23bcd', email: 'sidney.humphrey@example.com', visibleRegistrationNumber: '01234568' }, - { id: 3, firstName: 'Elane', lastName: 'Cravens', name: 'Elane Cravens', login: 'ga34cde', email: 'elane.cravens@example.com', visibleRegistrationNumber: '01234569' }, -] as User[]; - -export const mockNonTeamStudents = [ - { id: 4, firstName: 'Ned', lastName: 'Atkins', name: 'Ned Atkins', login: 'ga45def', email: 'ned.atkins@example.com' }, - { id: 5, firstName: 'Debbie', lastName: 'Roman', name: 'Debbie Roman', login: 'ga56efg', email: 'debbie.roman@example.com' }, - { id: 6, firstName: 'Jeffrey', lastName: 'McGill', name: 'Jeffrey McGill', login: 'ga67ghi', email: 'jeffrey.mcgill@example.com' }, -] as User[]; - -export const mockSourceTeamStudents = [ - { id: 7, firstName: 'Alice', lastName: 'McCarthy', name: 'Alice McCarthy', login: 'ga78abc', email: 'alice.mccarthy@example.com', visibleRegistrationNumber: '11234567' }, - { id: 8, firstName: 'Lena', lastName: 'Dudley', name: 'Lena Dudley', login: 'ga89bcd', email: 'lena.dudley@example.com', visibleRegistrationNumber: '11234568' }, - { id: 9, firstName: 'Thomas', lastName: 'Smith', name: 'Thomas Smith', login: 'ga90cde', email: 'thomas.smith@example.com', visibleRegistrationNumber: '11234569' }, -] as User[]; - -const teamAssignmentConfig = { minTeamSize: 1, maxTeamSize: 5 } as TeamAssignmentConfig; // note: size of mockTeamStudents above should conform - -const mockCourse = { id: 1 }; - -export const mockExercise = { id: 1, title: 'Programming exercise', teamAssignmentConfig, course: mockCourse } as ProgrammingExercise; -export const mockSourceExercise = { id: 2, title: 'Source Programming exercise', teamAssignmentConfig, course: mockCourse } as ProgrammingExercise; - -export const mockEmptyTeam = { - students: [], -} as unknown as Team; - -const mockTeamFromServer = { - id: 1, - name: 'Team 1', - shortName: 'team1', - students: mockTeamStudents, - owner: { id: 1 } as User, - createdBy: 'tutor1', - createdDate: new Date(), - lastModifiedDate: new Date(), -}; - -export const mockTeam = { - id: 1, - name: 'Team 1', - shortName: 'team1', - students: mockTeamStudents, - owner: { id: 1 } as User, - createdBy: 'tutor1', - createdDate: dayjs(mockTeamFromServer.createdDate), - lastModifiedDate: dayjs(mockTeamFromServer.lastModifiedDate), -} as Team; - -export const mockTeams = [ - mockTeam, - { id: 2, name: 'Team 2', shortName: 'team2', students: [], owner: { id: 1 } as User, createdBy: 'tutor1', createdDate: dayjs() } as Team, - { id: 3, name: 'Team 3', shortName: 'team3', students: [], owner: { id: 1 } as User, createdBy: 'tutor1', createdDate: dayjs() } as Team, -]; - -export const mockSourceTeam = { - id: 1, - name: 'Team 4', - shortName: 'team4', - students: mockSourceTeamStudents, - owner: { id: 1 } as User, - createdBy: 'tutor1', - createdDate: dayjs(), -} as Team; - -export const mockSourceTeams = [ - mockSourceTeam, - { id: 2, name: 'Team 5', shortName: 'team5', students: [], owner: { id: 1 } as User, createdBy: 'tutor1', createdDate: dayjs() } as Team, - { id: 3, name: 'Team 6', shortName: 'team6', students: [], owner: { id: 1 } as User, createdBy: 'tutor1', createdDate: dayjs() } as Team, -]; - -export const mockShortNames = { - existing: 'team1', - nonExisting: 'team2', -}; - -export const mockFileStudents = [ - { firstName: 'Jack', lastName: 'Doe', username: 'jack_doe', teamName: 'File Team 1' } as StudentWithTeam, - { firstName: 'Jackie', lastName: 'Doen', username: 'jackie_doen', teamName: 'File Team 1' } as StudentWithTeam, - { firstName: 'Alyson', lastName: 'Smithson', registrationNumber: '23458', teamName: 'File Team 2' } as StudentWithTeam, - { firstName: 'Alysia', lastName: 'Smith', registrationNumber: '23459', teamName: 'File Team 2' } as StudentWithTeam, -]; - -export const mockFileTeamsConverted: Team[] = [ - { - ...new Team(), - name: 'File Team 1', - shortName: 'fileteam1', - students: [ - { ...new User(), firstName: 'Jack', lastName: 'Doe', login: 'jack_doe', name: 'Jack Doe', visibleRegistrationNumber: undefined } as User, - { ...new User(), firstName: 'Jackie', lastName: 'Doen', login: 'jackie_doen', name: 'Jackie Doen', visibleRegistrationNumber: undefined } as User, - ], - } as Team, - { - ...new Team(), - name: 'File Team 2', - shortName: 'fileteam2', - students: [ - { ...new User(), firstName: 'Alyson', lastName: 'Smithson', login: undefined, visibleRegistrationNumber: '23458', name: 'Alyson Smithson' } as User, - { ...new User(), firstName: 'Alysia', lastName: 'Smith', login: undefined, visibleRegistrationNumber: '23459', name: 'Alysia Smith' } as User, - ], - } as Team, -]; - -export const mockTeamSearchUsers = [...mockTeamStudents, ...mockNonTeamStudents].map((student) => ({ - ...student, - assignedTeamId: mockTeamStudents.includes(student) ? mockTeam.id : null, -})) as TeamSearchUser[]; - -export class MockTeamService implements ITeamService { - create(exercise: Exercise, team: Team) { - return MockTeamService.response({ ...team, id: 1 }); - } - - update(exercise: Exercise, team: Team) { - return MockTeamService.response(team); - } - - find(exercise: Exercise, teamId: number) { - return MockTeamService.response(mockTeam); - } - - findAllByExerciseId(exerciseId: number, teamOwnerId?: number) { - return MockTeamService.response(mockTeams); - } - - delete(exercise: Exercise, teamId: number) { - return MockTeamService.response({}); - } - - existsByShortName(course: Course, shortName: string) { - return MockTeamService.response(shortName === mockShortNames.existing); - } - - searchInCourseForExerciseTeam(course: Course, exercise: Exercise, loginOrName: string) { - return MockTeamService.response(mockTeamSearchUsers); - } - - importTeamsFromSourceExercise(exercise: Exercise, sourceExercise: Exercise, importStrategy: TeamImportStrategyType) { - return MockTeamService.response(mockTeams); - } - - importTeams(exercise: Exercise, teams: Team[], importStrategyType: TeamImportStrategyType) { - return MockTeamService.response(mockTeams); - } - - findCourseWithExercisesAndParticipationsForTeam(course: Course, team: Team): Observable> { - return MockTeamService.response({ ...mockCourse, exercises: [{ ...(mockExercise as Exercise), teams: [mockTeam] }] } as Course); - } - - exportTeams(teams: Team[]) {} - - // helper method - private static response(entity: T) { - return of({ body: entity }) as Observable>; - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-text-editor.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-text-editor.service.ts deleted file mode 100644 index 8beed98b9dd6..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-text-editor.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { of } from 'rxjs'; -import { Language } from 'app/core/course/shared/entities/course.model'; - -export class MockTextEditorService { - get = (participationId: number) => of(); - predictLanguage = (text: string): Language => Language.ENGLISH; -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-text-submission.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-text-submission.service.ts deleted file mode 100644 index e644aa4edcb1..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-text-submission.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { TextSubmission } from 'app/text/shared/entities/text-submission.model'; -import { HttpResponse } from '@angular/common/http'; - -type EntityResponseType = HttpResponse; - -export class MockTextSubmissionService { - update = (textSubmission: TextSubmission, exerciseId: number): Observable => of({ body: textSubmission } as HttpResponse); -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-theme.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-theme.service.ts deleted file mode 100644 index 6d4ebebe83b7..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-theme.service.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { signal } from '@angular/core'; -import { Theme } from 'app/core/theme/shared/theme.service'; - -export class MockThemeService { - private _currentTheme = signal(Theme.LIGHT); - public readonly currentTheme = this._currentTheme.asReadonly(); - - private _userPreference = signal(undefined); - public readonly userPreference = this._userPreference.asReadonly(); - - public applyThemePreference(preference: Theme | undefined) { - this._userPreference.set(preference); - this._currentTheme.set(preference ?? Theme.LIGHT); - } - - public print() {} -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-translate.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-translate.service.ts deleted file mode 100644 index 45045c1f4af7..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-translate.service.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; -import { LangChangeEvent } from '@ngx-translate/core'; -import { BehaviorSubject, Observable, of, Subject } from 'rxjs'; -import { LANGUAGES } from 'app/core/language/shared/language.constants'; -import { ActivatedRouteSnapshot } from '@angular/router'; - -export const TRANSLATED_STRING = ''; - -export class MockTranslateService { - onLangChangeSubject: Subject = new Subject(); - onTranslationChangeSubject: Subject = new Subject(); - onDefaultLangChangeSubject: Subject = new Subject(); - isLoadedSubject: BehaviorSubject = new BehaviorSubject(true); - - onLangChange: Observable = this.onLangChangeSubject.asObservable(); - onTranslationChange: Observable = this.onTranslationChangeSubject.asObservable(); - onDefaultLangChange: Observable = this.onDefaultLangChangeSubject.asObservable(); - isLoaded: Observable = this.isLoadedSubject.asObservable(); - - currentLang: string; - - languages: string[] = ['de']; - - get(content: string): Observable { - return of(TRANSLATED_STRING + content); - } - - use(lang: string): void { - this.currentLang = lang; - this.onLangChangeSubject.next({ lang } as LangChangeEvent); - } - - addLangs(langs: string[]): void { - this.languages = [...this.languages, ...langs]; - } - - getBrowserLang(): string { - return ''; - } - - getLangs(): string[] { - return this.languages; - } - - getTranslation(): Observable { - return of({}); - } - - instant(key: string | string[], interpolateParams?: object): string { - return TRANSLATED_STRING + key.toString(); - } - - setDefaultLang(lang: string): void { - this.onDefaultLangChangeSubject.next(lang); - } -} - -export class MockLanguageHelper { - private _language: BehaviorSubject = new BehaviorSubject('en'); - - /** - * Get all supported ISO_639-1 language codes. - */ - getAll(): string[] { - return LANGUAGES; - } - - get language(): Observable { - return this._language.asObservable(); - } - updateTitle(titleKey?: string) {} - - // @ts-ignore - private getPageTitle(routeSnapshot: ActivatedRouteSnapshot) { - return ''; - } - - public determinePreferredLanguage(): string { - return 'en'; - } -} - -@Pipe({ - name: 'artemisTranslate', -}) -export class TranslatePipeMock implements PipeTransform { - public name = 'artemisTranslate'; - - public transform(query: string, ...args: any[]): any { - return query + (args && args.length > 0 ? ': ' + JSON.stringify(args) : ''); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-user-settings.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-user-settings.service.ts deleted file mode 100644 index 33042434985b..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-user-settings.service.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Subject } from 'rxjs'; -import { NotificationSetting } from 'app/core/user/settings/notification-settings/notification-settings-structure'; - -export class MockUserSettingsService { - private applyNewChangesSource = new Subject(); - userSettingsChangeEvent = this.applyNewChangesSource.asObservable(); - - loadSettings = () => {}; - loadSettingsSuccessAsSettingsStructure = () => {}; - loadSettingsSuccessAsIndividualSettings = () => [] as NotificationSetting[]; - saveSettings = () => {}; - saveSettingsSuccess = () => {}; - extractIndividualSettingsFromSettingsStructure = () => {}; - - sendApplyChangesEvent(message: string): void { - this.applyNewChangesSource.next(message); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-user.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-user.service.ts deleted file mode 100644 index f5fb835ffa8f..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-user.service.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { User } from 'app/core/user/user.model'; -import { HttpHeaders, HttpResponse } from '@angular/common/http'; -import { Authority } from 'app/shared/constants/authority.constants'; - -export class MockUserService { - query(req?: any): Observable> { - return of({ - body: [ - new User( - 1, - 'ga59pelTest', - 'Alexandros', - 'Tsakpinis', - 'alexandros.tsakpinis@tum.de', - false, - 'en', - [Authority.USER, Authority.ADMIN], - ['tumuser', 'eist2019students'], - 'anonymousUser', - new Date('2020-03-03T09:01:43Z'), - 'ga59pel', - new Date('2020-03-22T13:02:10Z'), - ), - ], - headers: new HttpHeaders({ - 'X-Total-Count': '1', - link: '; rel="last",; rel="first"', - }), - } as HttpResponse); - } - - updateLastNotificationRead(): Observable> { - return of({ - body: {} as User, - headers: new HttpHeaders(), - } as HttpResponse); - } - - updateNotificationVisibility(showAllNotifications: boolean): Observable> { - return of({ - headers: new HttpHeaders(), - } as HttpResponse); - } - - initializeLTIUser(): Observable> { - return of({ headers: new HttpHeaders(), body: { password: '' } } as HttpResponse<{ password: string }>); - } -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-view-container-ref.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-view-container-ref.service.ts deleted file mode 100644 index 8fdbfc270859..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-view-container-ref.service.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class MockViewContainerRef { - clear(): void {} - - createEmbeddedView(templateRef: any, context?: any, index?: any): any {} -} diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-websocket.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-websocket.service.ts deleted file mode 100644 index 58e7f79a2f51..000000000000 --- a/src/test/javascript/spec/helpers/mocks/service/mock-websocket.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Observable, of } from 'rxjs'; -import { ConnectionState, IWebsocketService } from 'app/shared/service/websocket.service'; - -export class MockWebsocketService implements IWebsocketService { - connect = () => {}; - - disableReconnect(): void {} - - disconnect(): void {} - - enableReconnect(): void {} - - isConnected(): boolean { - return true; - } - - receive(): Observable { - return of(); - } - - send(path: string, data: any): void {} - - stompFailureCallback(): void {} - - subscribe(): IWebsocketService { - return this; - } - - unsubscribe(): void {} - - state = of(new ConnectionState(true, true, false)); - get connectionState(): Observable { - return this.state; - } -} diff --git a/src/test/javascript/spec/helpers/mutable.ts b/src/test/javascript/spec/helpers/mutable.ts deleted file mode 100644 index 67a8f8f052ec..000000000000 --- a/src/test/javascript/spec/helpers/mutable.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * The Mutable Type overrides the Wrapped Types readonly annotations, making all properties writable. - */ -export type Mutable = { - -readonly [P in keyof T]: T[P]; -}; diff --git a/src/test/javascript/spec/helpers/on-push-change-detection.helper.ts b/src/test/javascript/spec/helpers/on-push-change-detection.helper.ts deleted file mode 100644 index a7be46985b47..000000000000 --- a/src/test/javascript/spec/helpers/on-push-change-detection.helper.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ChangeDetectorRef } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; - -/** - * Changes in components using OnPush strategy are only applied once when calling .detectChanges(), - * This function solves this issue. - * - * Source: https://gist.github.com/ali-kamalizade/14f7f0ab19f6592adf2f05cd6215dabf#file-on-push-change-detection-helper-ts - */ -export async function runOnPushChangeDetection(fixture: ComponentFixture): Promise { - const changeDetectorRef = fixture.debugElement.injector.get(ChangeDetectorRef); - changeDetectorRef.detectChanges(); - return fixture.whenStable(); -} diff --git a/src/test/javascript/spec/helpers/sample/build-logs.ts b/src/test/javascript/spec/helpers/sample/build-logs.ts deleted file mode 100644 index 5aa6037f8e36..000000000000 --- a/src/test/javascript/spec/helpers/sample/build-logs.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Annotation } from 'app/programming/shared/code-editor/monaco/code-editor-monaco.component'; - -export const buildLogs = [ - { - time: '2019-05-15T10:32:11+02:00', - log: '[ERROR] COMPILATION ERROR : ', - }, - { - time: '2019-05-15T10:32:11+02:00', - log: '[ERROR] /var/application-data/jenkins/xml-data/build-dir/COURSEPROGSHORT-BASE-JOB1/assignment/src/todo/main/BubbleSort.java:[8,12] cannot find symbol', - }, - { - time: '2019-05-15T10:32:11+02:00', - log: '  symbol:   class voi', - }, - { - time: '2019-05-15T10:32:11+02:00', - log: '  location: class todo.main.BubbleSort', - }, - { - time: '2019-05-15T10:32:11+02:00', - log: '[INFO] 1 error', - }, -]; - -export const extractedBuildLogErrors = [ - { - fileName: 'src/todo/main/BubbleSort.java', - type: 'error', - row: 7, - column: 11, - text: 'cannot find symbol', - timestamp: 1557909131000, - }, -] as Array; - -export const extractedErrorFiles = ['src/todo/main/BubbleSort.java']; diff --git a/src/test/javascript/spec/helpers/sample/conversationExampleModels.ts b/src/test/javascript/spec/helpers/sample/conversationExampleModels.ts deleted file mode 100644 index f6f2bfd222e7..000000000000 --- a/src/test/javascript/spec/helpers/sample/conversationExampleModels.ts +++ /dev/null @@ -1,135 +0,0 @@ -import dayjs from 'dayjs/esm'; -import { ChannelDTO, ChannelSubType } from 'app/communication/shared/entities/conversation/channel.model'; -import { ConversationUserDTO } from 'app/communication/shared/entities/conversation/conversation-user-dto.model'; -import { GroupChatDTO } from 'app/communication/shared/entities/conversation/group-chat.model'; -import { OneToOneChatDTO } from 'app/communication/shared/entities/conversation/one-to-one-chat.model'; - -export const generateExampleChannelDTO = ({ - id = 1, - name = 'general', - description = 'general channel', - topic = 'general', - isPublic = true, - isArchived = false, - isChannelModerator = true, - hasChannelModerationRights = true, - creationDate = dayjs(), - lastMessageDate = dayjs(), - lastReadDate = dayjs(), - numberOfMembers = 10, - creator = { id: 1, login: 'login', firstName: 'Kaddl', lastName: 'Garching' } as ConversationUserDTO, - isCreator = true, - isFavorite = false, - isHidden = false, - isMuted = false, - isMember = true, - isAnnouncementChannel = false, - isCourseWide = false, - unreadMessagesCount = 0, - tutorialGroupTitle = undefined, - tutorialGroupId = undefined, - subType = ChannelSubType.GENERAL, - subTypeReferenceId = undefined, -}: ChannelDTO) => { - const exampleChannelDTO = new ChannelDTO(); - exampleChannelDTO.id = id; - exampleChannelDTO.name = name; - exampleChannelDTO.description = description; - exampleChannelDTO.topic = topic; - exampleChannelDTO.isPublic = isPublic; - exampleChannelDTO.isArchived = isArchived; - exampleChannelDTO.isChannelModerator = isChannelModerator; - exampleChannelDTO.hasChannelModerationRights = hasChannelModerationRights; - exampleChannelDTO.creationDate = creationDate; - exampleChannelDTO.lastMessageDate = lastMessageDate; - exampleChannelDTO.lastReadDate = lastReadDate; - exampleChannelDTO.numberOfMembers = numberOfMembers; - exampleChannelDTO.creator = creator; - exampleChannelDTO.isCreator = isCreator; - exampleChannelDTO.isFavorite = isFavorite; - exampleChannelDTO.isHidden = isHidden; - exampleChannelDTO.isMuted = isMuted; - exampleChannelDTO.isMember = isMember; - exampleChannelDTO.isAnnouncementChannel = isAnnouncementChannel; - exampleChannelDTO.isCourseWide = isCourseWide; - exampleChannelDTO.unreadMessagesCount = unreadMessagesCount; - exampleChannelDTO.tutorialGroupTitle = tutorialGroupTitle; - exampleChannelDTO.tutorialGroupId = tutorialGroupId; - exampleChannelDTO.subType = subType; - exampleChannelDTO.subTypeReferenceId = subTypeReferenceId; - - return exampleChannelDTO; -}; - -export const generateExampleGroupChatDTO = ({ - id = 1, - name = 'awesome-group', - creationDate = dayjs(), - lastMessageDate = dayjs(), - lastReadDate = dayjs(), - numberOfMembers = 2, - creator = { id: 1, login: 'login', firstName: 'Kaddl', lastName: 'Garching' } as ConversationUserDTO, - members = [ - { id: 1, login: 'login', firstName: 'Kaddl', lastName: 'Garching' } as ConversationUserDTO, - { id: 2, login: 'login2', firstName: 'Kaddl2', lastName: 'Garching2' } as ConversationUserDTO, - ], - isCreator = true, - isFavorite = false, - isHidden = false, - isMuted = false, - isMember = true, - unreadMessagesCount = 0, -}: GroupChatDTO) => { - const exampleGroupChatDTO = new GroupChatDTO(); - exampleGroupChatDTO.id = id; - exampleGroupChatDTO.name = name; - exampleGroupChatDTO.creationDate = creationDate; - exampleGroupChatDTO.lastMessageDate = lastMessageDate; - exampleGroupChatDTO.lastReadDate = lastReadDate; - exampleGroupChatDTO.numberOfMembers = numberOfMembers; - exampleGroupChatDTO.creator = creator; - exampleGroupChatDTO.isCreator = isCreator; - exampleGroupChatDTO.isFavorite = isFavorite; - exampleGroupChatDTO.isHidden = isHidden; - exampleGroupChatDTO.isMuted = isMuted; - exampleGroupChatDTO.members = members; - exampleGroupChatDTO.isMember = isMember; - exampleGroupChatDTO.unreadMessagesCount = unreadMessagesCount; - - return exampleGroupChatDTO; -}; - -export const generateOneToOneChatDTO = ({ - id = 1, - creationDate = dayjs(), - lastMessageDate = dayjs(), - lastReadDate = dayjs(), - numberOfMembers = 2, - creator = { id: 1, login: 'login', firstName: 'Kaddl', lastName: 'Garching' } as ConversationUserDTO, - members = [ - { id: 1, login: 'login', firstName: 'Kaddl', lastName: 'Garching' } as ConversationUserDTO, - { id: 2, login: 'login2', firstName: 'Kaddl2', lastName: 'Garching2' } as ConversationUserDTO, - ], - isCreator = true, - isFavorite = false, - isHidden = false, - isMuted = false, - isMember = true, - unreadMessagesCount = 0, -}: GroupChatDTO) => { - const exampleOneToOneChatDTO = new OneToOneChatDTO(); - exampleOneToOneChatDTO.id = id; - exampleOneToOneChatDTO.creationDate = creationDate; - exampleOneToOneChatDTO.lastMessageDate = lastMessageDate; - exampleOneToOneChatDTO.lastReadDate = lastReadDate; - exampleOneToOneChatDTO.numberOfMembers = numberOfMembers; - exampleOneToOneChatDTO.creator = creator; - exampleOneToOneChatDTO.isCreator = isCreator; - exampleOneToOneChatDTO.isFavorite = isFavorite; - exampleOneToOneChatDTO.isHidden = isHidden; - exampleOneToOneChatDTO.isMuted = isMuted; - exampleOneToOneChatDTO.members = members; - exampleOneToOneChatDTO.isMember = isMember; - exampleOneToOneChatDTO.unreadMessagesCount = unreadMessagesCount; - return exampleOneToOneChatDTO; -}; diff --git a/src/test/javascript/spec/helpers/sample/exam-user-import/UserImportWithRoomAndSeatInfo.csv b/src/test/javascript/spec/helpers/sample/exam-user-import/UserImportWithRoomAndSeatInfo.csv deleted file mode 100644 index ca757f27e4f9..000000000000 --- a/src/test/javascript/spec/helpers/sample/exam-user-import/UserImportWithRoomAndSeatInfo.csv +++ /dev/null @@ -1,5 +0,0 @@ -login,registrationNumber,email,room,firstname,lastname,seat -artemis_test_user_2,03756882,krusche+testuser_2@in.tum.de, 101.2,ArTEMiS,Test User 2, 22F -artemis_test_user_3,03756883,krusche+testuser_3@in.tum.de, 101.2,ArTEMiS,Test User 3, 28F -artemis_test_user_4,03756884,krusche+testuser_4@in.tum.de, 101.2,ArTEMiS,Test User 4, 35F -artemis_test_user_5,03756885,krusche+testuser_5@in.tum.de, 101.2,ArTEMiS,Test User 5, 26F diff --git a/src/test/javascript/spec/helpers/sample/iris-sample-data.ts b/src/test/javascript/spec/helpers/sample/iris-sample-data.ts deleted file mode 100644 index b327f2d80e76..000000000000 --- a/src/test/javascript/spec/helpers/sample/iris-sample-data.ts +++ /dev/null @@ -1,110 +0,0 @@ -import dayjs from 'dayjs/esm'; -import { ExerciseType } from 'app/exercise/shared/entities/exercise/exercise.model'; -import { IrisAssistantMessage, IrisSender, IrisUserMessage } from 'app/iris/shared/entities/iris-message.model'; -import { IrisMessageContentType, IrisTextMessageContent } from 'app/iris/shared/entities/iris-content-type.model'; -import { IrisSession } from 'app/iris/shared/entities/iris-session.model'; -import { IrisChatWebsocketDTO, IrisChatWebsocketPayloadType } from 'app/iris/shared/entities/iris-chat-websocket-dto.model'; -import { IrisStageStateDTO } from 'app/iris/shared/entities/iris-stage-dto.model'; -import { ProgrammingExercise } from 'app/programming/shared/entities/programming-exercise.model'; -import { HttpResponse } from '@angular/common/http'; - -const map = new Map(); -map.set('model', 'gpt-4'); - -export const mockMessageContent = { - type: IrisMessageContentType.TEXT, - textContent: 'Hello, world!', -} as IrisTextMessageContent; - -export const irisExercise = { id: 1, title: 'Metis Exercise', type: ExerciseType.PROGRAMMING } as ProgrammingExercise; - -export const mockServerMessage = { - sender: IrisSender.LLM, - id: 1, - content: [mockMessageContent], - sentAt: dayjs(), -} as IrisAssistantMessage; - -export const mockServerMessage2 = { - sender: IrisSender.LLM, - id: 4, - content: [mockMessageContent], - sentAt: dayjs(), -} as IrisAssistantMessage; - -export const mockClientMessage = { - id: 2, - sender: IrisSender.USER, - content: [mockMessageContent], - sentAt: dayjs(), -} as IrisUserMessage; - -export const mockWebsocketServerMessage = { - type: IrisChatWebsocketPayloadType.MESSAGE, - message: mockServerMessage2, - stages: [], -} as IrisChatWebsocketDTO; - -export const mockWebsocketClientMessage = { - type: IrisChatWebsocketPayloadType.MESSAGE, - message: mockClientMessage, -} as IrisChatWebsocketDTO; - -export const mockWebsocketStatusMessage = { - type: IrisChatWebsocketPayloadType.STATUS, - stages: [ - { - name: 'Stage 1', - state: IrisStageStateDTO.IN_PROGRESS, - }, - ], -} as IrisChatWebsocketDTO; - -export const mockConversation = { - id: 1, - exercise: irisExercise, - messages: [mockClientMessage, mockServerMessage], -} as IrisSession; - -export const mockConversationWithNoMessages = { - id: 1, - exercise: irisExercise, - messages: [], -} as IrisSession; - -export const mockServerSessionHttpResponse = { - body: mockConversation, -} as HttpResponse; - -export const mockServerSessionHttpResponseWithEmptyConversation = { - body: { ...mockConversationWithNoMessages, id: 123 }, -} as HttpResponse; - -/** - * Mocks a user message with the given content. - * @param content - the content of the message - */ -export function mockUserMessageWithContent(content: string): IrisUserMessage { - return { - sender: IrisSender.USER, - id: 3, - content: [{ textContent: content } as IrisTextMessageContent], - sentAt: dayjs(), - } as IrisUserMessage; -} - -/** - * Mocks a server response with a conversation and an id. - * @param id - the id of the conversation - * @param empty - if true, the conversation will have no messages, defaults to false - */ -export function mockServerSessionHttpResponseWithId(id: number, empty: boolean = false): HttpResponse { - if (empty) { - return { - body: { ...mockConversationWithNoMessages, id }, - } as HttpResponse; - } - return { - body: { ...mockConversation, id }, - } as HttpResponse; -} diff --git a/src/test/javascript/spec/helpers/sample/metis-sample-data.ts b/src/test/javascript/spec/helpers/sample/metis-sample-data.ts deleted file mode 100644 index ea9894a534bd..000000000000 --- a/src/test/javascript/spec/helpers/sample/metis-sample-data.ts +++ /dev/null @@ -1,336 +0,0 @@ -import { Course, CourseInformationSharingConfiguration } from 'app/core/course/shared/entities/course.model'; -import { User } from 'app/core/user/user.model'; -import { VOTE_EMOJI_ID } from 'app/communication/metis.util'; -import { Reaction } from 'app/communication/shared/entities/reaction.model'; -import { Exercise, ExerciseType } from 'app/exercise/shared/entities/exercise/exercise.model'; -import { Lecture } from 'app/lecture/shared/entities/lecture.model'; -import { Post } from 'app/communication/shared/entities/post.model'; -import { AnswerPost } from 'app/communication/shared/entities/answer-post.model'; -import dayjs from 'dayjs/esm'; -import { Attachment } from 'app/lecture/shared/entities/attachment.model'; -import { ConversationParticipant } from 'app/communication/shared/entities/conversation/conversation-participant.model'; -import { Conversation, ConversationType } from 'app/communication/shared/entities/conversation/conversation.model'; -import { AttachmentUnit } from 'app/lecture/shared/entities/lecture-unit/attachmentUnit.model'; -import { Slide } from 'app/lecture/shared/entities/lecture-unit/slide.model'; -import { Channel, ChannelDTO, ChannelSubType } from 'app/communication/shared/entities/conversation/channel.model'; -import { Exam } from 'app/exam/shared/entities/exam.model'; -import { PlagiarismCase } from 'app/plagiarism/shared/entities/PlagiarismCase'; -import { LectureUnitType } from 'app/lecture/shared/entities/lecture-unit/lectureUnit.model'; - -export const metisSlide1 = { id: 1, slideNumber: 1, slideImagePath: 'directory/attachments/slides/Metis-Slide-1.png' } as Slide; -export const metisAttachment = { id: 1, name: 'Metis Attachment', link: 'directory/attachments/Metis-Attachment.pdf' } as Attachment; -export const metisAttachmentUnit = { id: 1, name: 'Metis Attachment Unit', attachment: metisAttachment, slides: [metisSlide1], type: LectureUnitType.ATTACHMENT } as AttachmentUnit; -export const metisLecture = { id: 1, title: 'Metis Lecture', attachments: [metisAttachment] } as Lecture; - -export const metisExam = { id: 1, title: 'Metis exam' } as Exam; -export const metisLecture2 = { id: 2, title: 'Second Metis Lecture' } as Lecture; -export const metisLecture3 = { id: 3, title: 'Third Metis Lecture 3', attachments: [metisAttachment], lectureUnits: [metisAttachmentUnit] } as Lecture; - -export const metisExercise = { id: 1, title: 'Metis Exercise', type: ExerciseType.TEXT } as Exercise; -export const metisExercise2 = { id: 2, title: 'Second Metis Exercise', type: ExerciseType.TEXT } as Exercise; - -export const metisUser1 = { id: 1, name: 'username1', login: 'login1', groups: ['metisStudents'] } as User; -export const metisUser2 = { id: 2, name: 'username2', login: 'login2', groups: ['metisStudents'] } as User; -export const metisTutor = { id: 4, name: 'username4', login: 'login4', groups: ['metisTutors'] } as User; - -export const metisTags = ['Tag1', 'Tag2']; - -export const metisUpVoteReactionUser1 = { id: 1, user: metisUser1, emojiId: VOTE_EMOJI_ID } as Reaction; -export const metisReactionUser2 = { id: 2, user: metisUser2, emojiId: 'smile', creationDate: undefined } as Reaction; -export const metisReactionToCreate = { emojiId: 'cheerio', creationDate: undefined } as Reaction; - -export const metisFaq1 = { id: 1, questionTitle: 'title', questionAnswer: 'answer' }; -export const metisFaq2 = { id: 2, questionTitle: 'title', questionAnswer: 'answer' }; -export const metisFaq3 = { id: 3, questionTitle: 'title', questionAnswer: 'answer' }; - -export const metisCourse = { - id: 1, - title: 'Metis Course', - exercises: [metisExercise, metisExercise2], - lectures: [metisLecture, metisLecture2, metisLecture3], - courseInformationSharingConfiguration: CourseInformationSharingConfiguration.COMMUNICATION_AND_MESSAGING, - groups: ['metisTutors', 'metisStudents', 'metisInstructors'], - faqs: [metisFaq1, metisFaq2, metisFaq3], -} as Course; - -export const metisResolvingAnswerPostUser1 = { - id: 1, - author: metisUser1, - content: 'metisAnswerPostUser3', - creationDate: undefined, - resolvesPost: true, -} as AnswerPost; - -export const metisAnswerPostUser2 = { - id: 2, - author: metisUser2, - content: 'metisAnswerPostUser3', - creationDate: undefined, -} as AnswerPost; -export const metisAnswerPostToCreateUser1 = { - author: metisUser1, - content: 'metisAnswerPostToCreateUser1', - creationDate: undefined, -} as AnswerPost; - -const courseWideChannelTemplate = { - type: ConversationType.CHANNEL, - course: metisCourse, - isAnnouncementChannel: false, - isArchived: false, - isPublic: true, - isCourseWide: true, - description: 'Course-wide channel', -}; - -const metisExerciseChannel = { - ...courseWideChannelTemplate, - id: 14, - name: 'exercise-channel', - exercise: metisExercise, -} as Channel; - -const metisLectureChannel = { - ...courseWideChannelTemplate, - id: 15, - name: 'lecture-channel', - lecture: metisLecture, -} as Channel; - -const metisTechSupportChannel = { - ...courseWideChannelTemplate, - id: 16, - name: 'tech-support', -} as Channel; - -const metisOrganizationChannel = { - ...courseWideChannelTemplate, - id: 17, - name: 'organization', -} as Channel; - -const metisRandomChannel = { - ...courseWideChannelTemplate, - id: 18, - name: 'random', -} as Channel; - -const metisAnnouncementChannel = { - ...courseWideChannelTemplate, - id: 19, - name: 'announcement', - isAnnouncementChannel: true, -} as Channel; - -export const metisPostTechSupport = { - id: 1, - author: metisUser1, - conversation: metisTechSupportChannel, - title: 'title', - content: 'metisPostTechSupport', - creationDate: undefined, -} as Post; - -export const metisPostRandom = { - id: 2, - author: metisUser1, - conversation: metisRandomChannel, - title: 'title', - content: 'metisPostRandom', - creationDate: undefined, -} as Post; - -export const metisPostOrganization = { - id: 3, - author: metisUser1, - conversation: metisOrganizationChannel, - title: 'title', - content: 'metisPostOrganization', - creationDate: undefined, -} as Post; - -export const metisAnnouncement = { - id: 4, - author: metisUser1, - conversation: metisAnnouncementChannel, - title: 'title', - content: 'metisPostOrganization', - creationDate: undefined, -} as Post; - -export const metisGeneralCourseWidePosts = [metisPostTechSupport, metisPostRandom, metisPostOrganization]; - -export const metisPostExerciseUser1 = { - id: 5, - author: metisUser1, - conversation: metisExerciseChannel, - title: 'title', - content: 'metisPostExerciseUser1', - creationDate: undefined, - isSaved: false, -} as Post; - -export const metisPostExerciseUser2 = { - id: 6, - author: metisUser2, - conversation: metisExerciseChannel, - title: 'title', - content: 'metisPostExerciseUser2', - creationDate: undefined, -} as Post; - -export const metisExercisePosts = [metisPostExerciseUser1, metisPostExerciseUser2]; - -export const metisPostLectureUser1 = { - id: 7, - author: metisUser1, - conversation: metisLectureChannel, - title: 'title', - content: 'metisPostLectureUser1', - creationDate: undefined, -} as Post; - -export const metisPostLectureUser2 = { - id: 8, - author: metisUser2, - conversation: metisLectureChannel, - title: 'title', - content: 'metisPostLectureUser2', - creationDate: undefined, - answers: [metisResolvingAnswerPostUser1], -} as Post; - -metisResolvingAnswerPostUser1.post = metisPostLectureUser2; - -export const metisLecturePosts = [metisPostLectureUser1, metisPostLectureUser2]; - -export const metisCoursePosts = metisGeneralCourseWidePosts.concat(metisExercisePosts, metisLecturePosts); - -export const metisPostToCreateUser1 = { - author: metisUser1, - content: 'metisAnswerToCreateUser1', - creationDate: undefined, -} as Post; - -export const unApprovedAnswerPost1 = { - id: 1, - creationDate: dayjs(), - content: 'not approved most recent', - resolvesPost: false, -} as AnswerPost; - -export const unApprovedAnswerPost2 = { - id: 2, - creationDate: dayjs().subtract(1, 'day'), - content: 'not approved', - resolvesPost: false, -} as AnswerPost; - -export const approvedAnswerPost = { - id: 2, - creationDate: undefined, - content: 'approved', - resolvesPost: true, -} as AnswerPost; - -export const sortedAnswerArray: AnswerPost[] = [approvedAnswerPost, unApprovedAnswerPost2, unApprovedAnswerPost1]; -export const unsortedAnswerArray: AnswerPost[] = [unApprovedAnswerPost1, unApprovedAnswerPost2, approvedAnswerPost]; - -export const post = { - id: 1, - creationDate: undefined, - answers: unsortedAnswerArray, -} as Post; - -const conversationParticipantUser1 = { id: 1, user: metisUser1, unreadMessagesCount: 1 } as ConversationParticipant; - -const conversationParticipantUser2 = { id: 2, user: metisUser2, unreadMessagesCount: 0 } as ConversationParticipant; - -export const conversationBetweenUser1User2 = { - id: 1, - conversationParticipants: [conversationParticipantUser1, conversationParticipantUser2], - creationDate: undefined, - lastMessageDate: undefined, -} as Conversation; - -export const directMessageUser1 = { - id: 9, - author: metisUser1, - content: 'user1directMessageToUser2', - creationDate: undefined, - conversation: conversationBetweenUser1User2, -} as Post; - -export const directMessageUser2 = { - id: 10, - author: metisUser1, - content: 'user2directMessageToUser1', - creationDate: undefined, - conversation: conversationBetweenUser1User2, -} as Post; - -export const messagesBetweenUser1User2 = [directMessageUser1, directMessageUser2]; - -export const metisChannel = { - id: 21, - type: ConversationType.CHANNEL, - name: 'example-channel', - description: 'Example course-wide channel', - isAnnouncementChannel: false, - isArchived: false, - isPublic: true, - isCourseWide: true, -} as Channel; - -export const metisPostInChannel = { - id: 4, - author: metisUser1, - title: 'title', - content: 'metisPostOrganization', - creationDate: undefined, - conversation: metisChannel, -} as Post; - -export const plagiarismPost = { - id: 11, - author: metisUser1, - title: 'title', - content: 'plagiarism Case', - plagiarismCase: { id: 1 } as PlagiarismCase, -} as Post; - -export const metisGeneralChannelDTO = { - id: 17, - type: ConversationType.CHANNEL, - subType: ChannelSubType.GENERAL, - isCourseWide: true, - name: 'general-channel', -} as ChannelDTO; - -export const metisExerciseChannelDTO = { - id: 14, - type: ConversationType.CHANNEL, - subType: ChannelSubType.EXERCISE, - isCourseWide: true, - subTypeReferenceId: metisExercise.id, - - name: 'exercise-channel', -} as ChannelDTO; - -export const metisLectureChannelDTO = { - id: 15, - type: ConversationType.CHANNEL, - subType: ChannelSubType.LECTURE, - isCourseWide: true, - subTypeReferenceId: metisLecture.id, - name: 'lecture-channel', -} as ChannelDTO; - -export const metisExamChannelDTO = { - id: 20, - type: ConversationType.CHANNEL, - subType: ChannelSubType.EXAM, - isCourseWide: true, - subTypeReferenceId: metisExam.id, - name: 'exam-channel', -} as ChannelDTO; diff --git a/src/test/javascript/spec/helpers/sample/modeling/test-models/class-diagram.json b/src/test/javascript/spec/helpers/sample/modeling/test-models/class-diagram.json deleted file mode 100644 index 1d1686ea0726..000000000000 --- a/src/test/javascript/spec/helpers/sample/modeling/test-models/class-diagram.json +++ /dev/null @@ -1,269 +0,0 @@ -{ - "version": "3.0.0", - "type": "ClassDiagram", - "size": { - "width": 1280, - "height": 620 - }, - "interactive": { - "elements": { - "b390a813-dad9-4d6d-b3cd-732ce99d0a23": true, - "6f572312-066b-4678-9c03-5032f3ba9be9": true, - "2f67120e-b491-4222-beb1-79e87c2cf54d": true - }, - "relationships": {} - }, - "elements": { - "b234e5cb-33e3-4957-ae04-f7990ce8571a": { - "id": "b234e5cb-33e3-4957-ae04-f7990ce8571a", - "name": "Package", - "type": "Package", - "owner": null, - "bounds": { - "x": 0, - "y": 40, - "width": 470, - "height": 310 - } - }, - "ccac14e5-c828-4afb-ab97-0fb2a67e77d6": { - "id": "ccac14e5-c828-4afb-ab97-0fb2a67e77d6", - "name": "Class In Package", - "type": "Class", - "owner": "b234e5cb-33e3-4957-ae04-f7990ce8571a", - "bounds": { - "x": 90, - "y": 150, - "width": 200, - "height": 100 - }, - "attributes": ["6f572312-066b-4678-9c03-5032f3ba9be9"], - "methods": ["11aae531-3244-4d07-8d60-b6210789ffa3"] - }, - "6f572312-066b-4678-9c03-5032f3ba9be9": { - "id": "6f572312-066b-4678-9c03-5032f3ba9be9", - "name": "+ attribute: Type", - "type": "ClassAttribute", - "owner": "ccac14e5-c828-4afb-ab97-0fb2a67e77d6", - "bounds": { - "x": 90, - "y": 190, - "width": 200, - "height": 30 - } - }, - "11aae531-3244-4d07-8d60-b6210789ffa3": { - "id": "11aae531-3244-4d07-8d60-b6210789ffa3", - "name": "+ method()", - "type": "ClassMethod", - "owner": "ccac14e5-c828-4afb-ab97-0fb2a67e77d6", - "bounds": { - "x": 90, - "y": 220, - "width": 200, - "height": 30 - } - }, - "2f67120e-b491-4222-beb1-79e87c2cf54d": { - "id": "2f67120e-b491-4222-beb1-79e87c2cf54d", - "name": "Connected Class", - "type": "Class", - "owner": null, - "bounds": { - "x": 700, - "y": 70, - "width": 200, - "height": 100 - }, - "attributes": ["6a6aadfd-a2dd-44d7-b91a-271efba52072"], - "methods": ["5adfa0d4-e76c-4e98-8960-08abe615ec77"] - }, - "6a6aadfd-a2dd-44d7-b91a-271efba52072": { - "id": "6a6aadfd-a2dd-44d7-b91a-271efba52072", - "name": "+ attribute: Type", - "type": "ClassAttribute", - "owner": "2f67120e-b491-4222-beb1-79e87c2cf54d", - "bounds": { - "x": 700, - "y": 110, - "width": 200, - "height": 30 - } - }, - "5adfa0d4-e76c-4e98-8960-08abe615ec77": { - "id": "5adfa0d4-e76c-4e98-8960-08abe615ec77", - "name": "+ method()", - "type": "ClassMethod", - "owner": "2f67120e-b491-4222-beb1-79e87c2cf54d", - "bounds": { - "x": 700, - "y": 140, - "width": 200, - "height": 30 - } - }, - "e0dad7e7-f67b-4e4a-8845-6c5d801ea9ca": { - "id": "e0dad7e7-f67b-4e4a-8845-6c5d801ea9ca", - "name": "Sibling 2", - "type": "Class", - "owner": null, - "bounds": { - "x": 600, - "y": 390, - "width": 200, - "height": 100 - }, - "attributes": ["fea23cbc-8df0-4dcc-9d7a-eb86fbb2ce9d"], - "methods": ["3e0d47e6-7491-49ce-a8bb-ce1fc4f022d0"] - }, - "fea23cbc-8df0-4dcc-9d7a-eb86fbb2ce9d": { - "id": "fea23cbc-8df0-4dcc-9d7a-eb86fbb2ce9d", - "name": "+ attribute: Type", - "type": "ClassAttribute", - "owner": "e0dad7e7-f67b-4e4a-8845-6c5d801ea9ca", - "bounds": { - "x": 600, - "y": 430, - "width": 200, - "height": 30 - } - }, - "3e0d47e6-7491-49ce-a8bb-ce1fc4f022d0": { - "id": "3e0d47e6-7491-49ce-a8bb-ce1fc4f022d0", - "name": "+ method()", - "type": "ClassMethod", - "owner": "e0dad7e7-f67b-4e4a-8845-6c5d801ea9ca", - "bounds": { - "x": 600, - "y": 460, - "width": 200, - "height": 30 - } - }, - "d8b00149-7cf9-466b-8add-8d3274f84010": { - "id": "d8b00149-7cf9-466b-8add-8d3274f84010", - "name": "Sibling 1", - "type": "Class", - "owner": null, - "bounds": { - "x": 80, - "y": 390, - "width": 200, - "height": 100 - }, - "attributes": ["593e9ae3-a516-4322-b54f-e07c57729e01"], - "methods": ["b390a813-dad9-4d6d-b3cd-732ce99d0a23"] - }, - "593e9ae3-a516-4322-b54f-e07c57729e01": { - "id": "593e9ae3-a516-4322-b54f-e07c57729e01", - "name": "+ attribute: Type", - "type": "ClassAttribute", - "owner": "d8b00149-7cf9-466b-8add-8d3274f84010", - "bounds": { - "x": 80, - "y": 430, - "width": 200, - "height": 30 - } - }, - "b390a813-dad9-4d6d-b3cd-732ce99d0a23": { - "id": "b390a813-dad9-4d6d-b3cd-732ce99d0a23", - "name": "+ method()", - "type": "ClassMethod", - "owner": "d8b00149-7cf9-466b-8add-8d3274f84010", - "bounds": { - "x": 80, - "y": 460, - "width": 200, - "height": 30 - } - } - }, - "relationships": { - "5a9a4eb3-8281-4de4-b0f2-3e2f164574bd": { - "id": "5a9a4eb3-8281-4de4-b0f2-3e2f164574bd", - "name": "", - "type": "ClassBidirectional", - "owner": null, - "bounds": { - "x": 230, - "y": 0, - "width": 575, - "height": 70 - }, - "path": [ - { - "x": 570, - "y": 70 - }, - { - "x": 570, - "y": 0 - }, - { - "x": 5, - "y": 0 - }, - { - "x": 5, - "y": 40 - } - ], - "source": { - "direction": "Up", - "element": "2f67120e-b491-4222-beb1-79e87c2cf54d", - "multiplicity": "", - "role": "" - }, - "target": { - "direction": "Up", - "element": "b234e5cb-33e3-4957-ae04-f7990ce8571a", - "multiplicity": "", - "role": "" - } - }, - "7c4746e0-aeaa-444e-9412-0996c24e6479": { - "id": "7c4746e0-aeaa-444e-9412-0996c24e6479", - "name": "", - "type": "ClassBidirectional", - "owner": null, - "bounds": { - "x": 175, - "y": 490, - "width": 530, - "height": 40 - }, - "path": [ - { - "x": 5, - "y": 0 - }, - { - "x": 5, - "y": 40 - }, - { - "x": 525, - "y": 40 - }, - { - "x": 525, - "y": 0 - } - ], - "source": { - "direction": "Down", - "element": "d8b00149-7cf9-466b-8add-8d3274f84010", - "multiplicity": "", - "role": "" - }, - "target": { - "direction": "Down", - "element": "e0dad7e7-f67b-4e4a-8845-6c5d801ea9ca", - "multiplicity": "", - "role": "" - } - } - }, - "assessments": {} -} diff --git a/src/test/javascript/spec/helpers/sample/problemStatement.json b/src/test/javascript/spec/helpers/sample/problemStatement.json deleted file mode 100644 index e863787c2d7d..000000000000 --- a/src/test/javascript/spec/helpers/sample/problemStatement.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "problemStatement": "1. [task][Implement Bubble Sort](testBubbleSort) \n Implement the method `performSort(List)` in the class `BubbleSort`. Make sure to follow the Bubble Sort algorithm exactly. \n 2. [task][Implement Merge Sort](testMergeSort) \n Implement the method `performSort(List)` in the class `MergeSort`. Make sure to follow the Merge Sort algorithm exactly.", - "problemStatementRepeatedTestCases": "1. [task][Implement Bubble Sort](testBubbleSort, testBubbleSort) \n Implement the method `performSort(List)` in the class `BubbleSort`. Make sure to follow the Bubble Sort algorithm exactly. \n 2. [task][Implement Merge Sort](testBubbleSort) \n Implement the method `performSort(List)` in the class `MergeSort`. Make sure to follow the Merge Sort algorithm exactly.", - "problemStatementWithIds": "1. [task][Implement Bubble Sort](1) \n Implement the method `performSort(List)` in the class `BubbleSort`. Make sure to follow the Bubble Sort algorithm exactly. \n 2. [task][Implement Merge Sort](2) \n Implement the method `performSort(List)` in the class `MergeSort`. Make sure to follow the Merge Sort algorithm exactly.", - "problemStatementNoneExecutedRendered": "
    \n
  1. Implement Bubble Sort: artemisApp.editor.testStatusLabels.noResult
    \nImplement the method performSort(List<Date>) in the class BubbleSort. Make sure to follow the Bubble Sort algorithm exactly.
  2. \n
  3. Implement Merge Sort: artemisApp.editor.testStatusLabels.noResult
    \nImplement the method performSort(List<Date>) in the class MergeSort. Make sure to follow the Merge Sort algorithm exactly.
  4. \n
\n", - "problemStatementNoneExecutedHtml": "
    \n
  1. Implement Bubble Sort: artemisApp.editor.testStatusLabels.testFailing
    \nImplement the method performSort(List<Date>) in the class BubbleSort. Make sure to follow the Bubble Sort algorithm exactly.
  2. \n
  3. Implement Merge Sort: artemisApp.editor.testStatusLabels.testPassing
    \nImplement the method performSort(List<Date>) in the class MergeSort. Make sure to follow the Merge Sort algorithm exactly.
  4. \n
\n", - "problemStatementBothFailedRendered": "
    \n
  1. Implement Bubble Sort: artemisApp.editor.testStatusLabels.noResult
    \nImplement the method performSort(List<Date>) in the class BubbleSort. Make sure to follow the Bubble Sort algorithm exactly.
  2. \n
  3. Implement Merge Sort: artemisApp.editor.testStatusLabels.noResult
    \nImplement the method performSort(List<Date>) in the class MergeSort. Make sure to follow the Merge Sort algorithm exactly.
  4. \n
\n", - "problemStatementBothFailedHtml": "
    \n
  1. Implement Bubble Sort: artemisApp.editor.testStatusLabels.testFailing
    \nImplement the method performSort(List<Date>) in the class BubbleSort. Make sure to follow the Bubble Sort algorithm exactly.
  2. \n
  3. Implement Merge Sort: artemisApp.editor.testStatusLabels.testPassing
    \nImplement the method performSort(List<Date>) in the class MergeSort. Make sure to follow the Merge Sort algorithm exactly.
  4. \n
\n", - "problemStatementBubbleSortFailsRendered": "
    \n
  1. Implement Bubble Sort: artemisApp.editor.testStatusLabels.noResult
    \nImplement the method performSort(List<Date>) in the class BubbleSort. Make sure to follow the Bubble Sort algorithm exactly.
  2. \n
  3. Implement Merge Sort: artemisApp.editor.testStatusLabels.noResult
    \nImplement the method performSort(List<Date>) in the class MergeSort. Make sure to follow the Merge Sort algorithm exactly.
  4. \n
\n", - "problemStatementBubbleSortNotExecutedHtml": "
    \n
  1. Implement Bubble SortartemisApp.editor.testStatusLabels.totalTestsPassing
    \nImplement the method performSort(List<Date>) in the class BubbleSort. Make sure to follow the Bubble Sort algorithm exactly.
  2. \n
  3. Implement Merge SortartemisApp.editor.testStatusLabels.totalTestsPassing
    \nImplement the method performSort(List<Date>) in the class MergeSort. Make sure to follow the Merge Sort algorithm exactly.
  4. \n
", - "problemStatementEmptySecondTask": "1. [task][Bubble Sort](1) \n Implement the method. \n 2. [task][Merge Sort]() \n Implement the method.", - "problemStatementEmptySecondTaskNotExecutedHtml": "
    \n
  1. Bubble SortartemisApp.editor.testStatusLabels.totalTestsPassing
    \nImplement the method.
  2. \n
  3. Merge SortartemisApp.editor.testStatusLabels.noTests
    \nImplement the method.
  4. \n
", - "problemStatementPlantUMLWithTest": "@startuml\nclass Policy {\n1)>+configure()\n2)>+testWithParenthesis()}\n@enduml" -} diff --git a/src/test/javascript/spec/helpers/sample/tutorialgroup/tutorialGroupExampleModels.ts b/src/test/javascript/spec/helpers/sample/tutorialgroup/tutorialGroupExampleModels.ts deleted file mode 100644 index db8396bb1624..000000000000 --- a/src/test/javascript/spec/helpers/sample/tutorialgroup/tutorialGroupExampleModels.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { TutorialGroup } from 'app/tutorialgroup/shared/entities/tutorial-group.model'; -import { Course } from 'app/core/course/shared/entities/course.model'; -import { TutorialGroupFormData, UserWithLabel } from 'app/tutorialgroup/manage/tutorial-groups/crud/tutorial-group-form/tutorial-group-form.component'; -import dayjs from 'dayjs/esm'; - -export const generateExampleTutorialGroup = ({ - id = 1, - title = 'Example', - capacity = 10, - campus = 'Example Campus', - language = 'ENGLISH', - additionalInformation = 'Example Additional Information', - isOnline = false, - teachingAssistant = { id: 1, login: 'Example TA', label: '(Example TA)' } as UserWithLabel, - isUserRegistered = false, - isUserTutor = false, - numberOfRegisteredUsers = 5, - course = { id: 1, title: 'Test Course' } as Course, - teachingAssistantName = 'Example TA', - teachingAssistantId = 1, - teachingAssistantImageUrl = 'test image', - courseTitle = 'Test Course', - tutorialGroupSchedule = { - id: 1, - dayOfWeek: 1, - startTime: '10:00', - endTime: '11:00', - repetitionFrequency: 1, - location: 'Example Location', - validFromInclusive: dayjs('2021-01-01'), - validToInclusive: dayjs('2021-01-01'), - }, -}: TutorialGroup) => { - const exampleTutorialGroup = new TutorialGroup(); - exampleTutorialGroup.id = id; - exampleTutorialGroup.title = title; - exampleTutorialGroup.capacity = capacity; - exampleTutorialGroup.campus = campus; - exampleTutorialGroup.language = language; - exampleTutorialGroup.additionalInformation = additionalInformation; - exampleTutorialGroup.isOnline = isOnline; - exampleTutorialGroup.teachingAssistant = teachingAssistant; - exampleTutorialGroup.isUserRegistered = isUserRegistered; - exampleTutorialGroup.isUserTutor = isUserTutor; - exampleTutorialGroup.numberOfRegisteredUsers = numberOfRegisteredUsers; - exampleTutorialGroup.course = course; - exampleTutorialGroup.teachingAssistantName = teachingAssistantName; - exampleTutorialGroup.teachingAssistantId = teachingAssistantId; - exampleTutorialGroup.teachingAssistantImageUrl = teachingAssistantImageUrl; - exampleTutorialGroup.courseTitle = courseTitle; - exampleTutorialGroup.tutorialGroupSchedule = tutorialGroupSchedule; - return exampleTutorialGroup; -}; - -export const tutorialGroupToTutorialGroupFormData = (entity: TutorialGroup): TutorialGroupFormData => { - return { - title: entity.title, - capacity: entity.capacity, - campus: entity.campus, - language: entity.language, - additionalInformation: entity.additionalInformation, - isOnline: entity.isOnline, - teachingAssistant: entity.teachingAssistant, - schedule: { - location: entity.tutorialGroupSchedule?.location, - dayOfWeek: entity.tutorialGroupSchedule?.dayOfWeek, - startTime: entity.tutorialGroupSchedule?.startTime, - endTime: entity.tutorialGroupSchedule?.endTime, - repetitionFrequency: entity.tutorialGroupSchedule?.repetitionFrequency, - // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain - period: [entity.tutorialGroupSchedule?.validFromInclusive?.toDate()!, entity.tutorialGroupSchedule?.validToInclusive?.toDate()!], - }, - }; -}; diff --git a/src/test/javascript/spec/helpers/sample/tutorialgroup/tutorialGroupFormsUtils.ts b/src/test/javascript/spec/helpers/sample/tutorialgroup/tutorialGroupFormsUtils.ts deleted file mode 100644 index 1bd1e3b87c54..000000000000 --- a/src/test/javascript/spec/helpers/sample/tutorialgroup/tutorialGroupFormsUtils.ts +++ /dev/null @@ -1,68 +0,0 @@ -// util methods for testing tutorial group forms as they all follow the same patterns - -import { ComponentFixture } from '@angular/core/testing'; -import { - TutorialGroupFreePeriodFormComponent, - TutorialGroupFreePeriodFormData, -} from 'app/tutorialgroup/manage/tutorial-free-periods/crud/tutorial-free-period-form/tutorial-group-free-period-form.component'; -import { - TutorialGroupSessionFormComponent, - TutorialGroupSessionFormData, -} from 'app/tutorialgroup/manage/tutorial-group-sessions/crud/tutorial-group-session-form/tutorial-group-session-form.component'; -import { TutorialGroupFormComponent, TutorialGroupFormData } from 'app/tutorialgroup/manage/tutorial-groups/crud/tutorial-group-form/tutorial-group-form.component'; -import { - TutorialGroupsConfigurationFormComponent, - TutorialGroupsConfigurationFormData, -} from 'app/tutorialgroup/manage/tutorial-groups-configuration/crud/tutorial-groups-configuration-form/tutorial-groups-configuration-form.component'; -import { runOnPushChangeDetection } from '../../on-push-change-detection.helper'; - -type SupportedForms = TutorialGroupFreePeriodFormComponent | TutorialGroupSessionFormComponent | TutorialGroupsConfigurationFormComponent | TutorialGroupFormComponent; -type SupportedFixtures = ComponentFixture; -type SupportedFormData = TutorialGroupFreePeriodFormData | TutorialGroupSessionFormData | TutorialGroupsConfigurationFormData | TutorialGroupFormData; - -export const generateClickSubmitButton = (component: SupportedForms, fixture: SupportedFixtures, expectedEventFormData?: SupportedFormData) => { - return (expectSubmitEvent: boolean) => { - const submitFormSpy = jest.spyOn(component, 'submitForm'); - const submitFormEventSpy = jest.spyOn(component.formSubmitted, 'emit'); - - const submitButton = fixture.debugElement.nativeElement.querySelector('#submitButton'); - submitButton.click(); - - return fixture.whenStable().then(() => { - if (expectSubmitEvent) { - expect(submitFormSpy).toHaveBeenCalledOnce(); - expect(submitFormEventSpy).toHaveBeenCalledOnce(); - expect(submitFormEventSpy).toHaveBeenCalledWith(expectedEventFormData); - } else { - expect(submitFormSpy).not.toHaveBeenCalled(); - expect(submitFormEventSpy).not.toHaveBeenCalled(); - } - }); - }; -}; - -export const generateTestFormIsInvalidOnMissingRequiredProperty = ( - component: SupportedForms, - fixture: SupportedFixtures, - setValidFormValues: () => void, - clickSubmit: (expectSubmitEvent: boolean) => void, -) => { - return (controlName: string, subFormName?: string) => { - setValidFormValues(); - - runOnPushChangeDetection(fixture); - expect(component.form.valid).toBeTrue(); - expect(component.isSubmitPossible).toBeTrue(); - - if (subFormName) { - component.form.get(subFormName)!.get(controlName)!.setValue(null); - } else { - component.form.get(controlName)!.setValue(undefined); - } - runOnPushChangeDetection(fixture); - expect(component.form.invalid).toBeTrue(); - expect(component.isSubmitPossible).toBeFalse(); - - clickSubmit(false); - }; -}; diff --git a/src/test/javascript/spec/helpers/sample/tutorialgroup/tutorialGroupFreePeriodExampleModel.ts b/src/test/javascript/spec/helpers/sample/tutorialgroup/tutorialGroupFreePeriodExampleModel.ts deleted file mode 100644 index b66da556aee2..000000000000 --- a/src/test/javascript/spec/helpers/sample/tutorialgroup/tutorialGroupFreePeriodExampleModel.ts +++ /dev/null @@ -1,66 +0,0 @@ -import dayjs from 'dayjs/esm'; -import { TutorialGroupFreePeriod } from 'app/tutorialgroup/shared/entities/tutorial-group-free-day.model'; -import { TutorialGroupFreePeriodFormData } from 'app/tutorialgroup/manage/tutorial-free-periods/crud/tutorial-free-period-form/tutorial-group-free-period-form.component'; -import { TutorialGroupFreePeriodDTO } from 'app/tutorialgroup/shared/service/tutorial-group-free-period.service'; -import { TutorialGroupFreePeriodsManagementComponent } from 'app/tutorialgroup/manage/tutorial-free-periods/tutorial-free-periods-management/tutorial-group-free-periods-management.component'; - -export const generateExampleTutorialGroupFreePeriod = ({ - id = 1, - start = dayjs.utc('2021-01-01T00:00:00'), - end = dayjs.utc('2021-01-01T23:59:59'), - reason = 'Example Reason', -}: TutorialGroupFreePeriod) => { - const examplePeriod = new TutorialGroupFreePeriod(); - examplePeriod.id = id; - // we get utc from the server --> will be converted to time zone of configuration - examplePeriod.start = start; - examplePeriod.end = end; - examplePeriod.reason = reason; - return examplePeriod; -}; - -export const tutorialGroupFreePeriodToTutorialGroupFreePeriodFormData = (entity: TutorialGroupFreePeriod, tz: string): TutorialGroupFreePeriodFormData => { - if (TutorialGroupFreePeriodsManagementComponent.isFreeDay(entity)) { - return { - startDate: entity.start!.tz(tz).toDate(), - endDate: undefined, - startTime: undefined, - endTime: undefined, - reason: entity.reason, - }; - } else if (TutorialGroupFreePeriodsManagementComponent.isFreePeriod(entity)) { - return { - startDate: entity.start!.tz(tz).toDate(), - endDate: entity.end!.tz(tz).toDate(), - startTime: undefined, - endTime: undefined, - reason: entity.reason, - }; - } else { - return { - startDate: entity.start!.tz(tz).toDate(), - endDate: undefined, - startTime: entity.start!.tz(tz).toDate(), - endTime: entity.end!.tz(tz).toDate(), - reason: entity.reason, - }; - } -}; - -export const formDataToTutorialGroupFreePeriodDTO = (formData: TutorialGroupFreePeriodFormData): TutorialGroupFreePeriodDTO => { - if (formData.endDate) { - return { - startDate: formData.startDate, - endDate: formData.endDate, - reason: formData.reason, - }; - } else { - const res = { - startDate: formData.startDate, - endDate: formData.startDate, - reason: formData.reason, - }; - res.endDate!.setHours(23, 59); - return res; - } -}; diff --git a/src/test/javascript/spec/helpers/sample/tutorialgroup/tutorialGroupSessionExampleModels.ts b/src/test/javascript/spec/helpers/sample/tutorialgroup/tutorialGroupSessionExampleModels.ts deleted file mode 100644 index 850fa8e8de0d..000000000000 --- a/src/test/javascript/spec/helpers/sample/tutorialgroup/tutorialGroupSessionExampleModels.ts +++ /dev/null @@ -1,40 +0,0 @@ -import dayjs from 'dayjs/esm'; -import { TutorialGroupSession, TutorialGroupSessionStatus } from 'app/tutorialgroup/shared/entities/tutorial-group-session.model'; -import { TutorialGroupSessionFormData } from 'app/tutorialgroup/manage/tutorial-group-sessions/crud/tutorial-group-session-form/tutorial-group-session-form.component'; -import { TutorialGroupSessionDTO } from 'app/tutorialgroup/shared/service/tutorial-group-session.service'; - -export const generateExampleTutorialGroupSession = ({ - id = 3, - start = dayjs.utc('2021-01-01T10:00:00'), - end = dayjs.utc('2021-01-01T11:00:00'), - location = 'Room 1', - status = TutorialGroupSessionStatus.ACTIVE, -}: TutorialGroupSession) => { - const exampleSession = new TutorialGroupSession(); - exampleSession.id = id; - // we get utc from the server --> will be converted to time zone of configuration - exampleSession.start = start; - exampleSession.end = end; - exampleSession.location = location; - exampleSession.status = status; - - return exampleSession; -}; - -export const tutorialGroupSessionToTutorialGroupSessionFormData = (entity: TutorialGroupSession, tz: string): TutorialGroupSessionFormData => { - return { - date: entity.start!.tz(tz).toDate(), - startTime: entity.start!.tz(tz).format('HH:mm:ss'), - endTime: entity.end!.tz(tz).format('HH:mm:ss'), - location: entity.location, - }; -}; - -export const formDataToTutorialGroupSessionDTO = (formData: TutorialGroupSessionFormData): TutorialGroupSessionDTO => { - return { - date: formData.date, - startTime: formData.startTime, - endTime: formData.endTime, - location: formData.location, - }; -}; diff --git a/src/test/javascript/spec/helpers/sample/tutorialgroup/tutorialGroupsConfigurationExampleModels.ts b/src/test/javascript/spec/helpers/sample/tutorialgroup/tutorialGroupsConfigurationExampleModels.ts deleted file mode 100644 index 652f84885b13..000000000000 --- a/src/test/javascript/spec/helpers/sample/tutorialgroup/tutorialGroupsConfigurationExampleModels.ts +++ /dev/null @@ -1,27 +0,0 @@ -import dayjs from 'dayjs/esm'; -import { TutorialGroupsConfiguration } from 'app/tutorialgroup/shared/entities/tutorial-groups-configuration.model'; -import { TutorialGroupsConfigurationFormData } from 'app/tutorialgroup/manage/tutorial-groups-configuration/crud/tutorial-groups-configuration-form/tutorial-groups-configuration-form.component'; - -export const generateExampleTutorialGroupsConfiguration = ({ - id = 1, - tutorialPeriodStartInclusive = dayjs('2021-01-01'), - tutorialPeriodEndInclusive = dayjs('2021-01-02'), - useTutorialGroupChannels = true, - usePublicTutorialGroupChannels = true, -}: TutorialGroupsConfiguration) => { - const exampleConfiguration = new TutorialGroupsConfiguration(); - exampleConfiguration.id = id; - exampleConfiguration.tutorialPeriodStartInclusive = tutorialPeriodStartInclusive; - exampleConfiguration.tutorialPeriodEndInclusive = tutorialPeriodEndInclusive; - exampleConfiguration.useTutorialGroupChannels = useTutorialGroupChannels; - exampleConfiguration.usePublicTutorialGroupChannels = usePublicTutorialGroupChannels; - return exampleConfiguration; -}; - -export const tutorialsGroupsConfigurationToFormData = (entity: TutorialGroupsConfiguration): TutorialGroupsConfigurationFormData => { - return { - period: [entity.tutorialPeriodStartInclusive!.toDate(), entity.tutorialPeriodEndInclusive!.toDate()], - useTutorialGroupChannels: entity.useTutorialGroupChannels, - usePublicTutorialGroupChannels: entity.usePublicTutorialGroupChannels, - }; -}; diff --git a/src/test/javascript/spec/helpers/sample/user-import/TUMonlineCourseExport.csv b/src/test/javascript/spec/helpers/sample/user-import/TUMonlineCourseExport.csv deleted file mode 100644 index 6b0be20eb48e..000000000000 --- a/src/test/javascript/spec/helpers/sample/user-import/TUMonlineCourseExport.csv +++ /dev/null @@ -1,6 +0,0 @@ -"GRUPPE";"TITEL";"FAMILIENNAME";"VORNAME";"MATRIKELNUMMER";"GESCHLECHT";"E-MAIL";"PLATZ";"STATUS_ERREICHT_AM";"ANMERKUNG";"KENNZAHL";"PRAEFERENZ";"STUDIUM";"SPO_KONTEXT_KENNUNG";"SPO_KONTEXT_NAME";"SEMESTER IM STUDIUM";"ANMELDEDATUM";"FACHSEMESTER";"ANMELDEVERFAHREN";"ANMELDEVERFAHREN KURZ";"ANMELDEVERFAHREN STATUS";"TEAMNAME" -"Standardgruppe";"";"Mustermann";"Max Moritz";" 01234567";"M";"max-moritz.mustermann@example.com";"Fixplatz";"28.02.2022 21:51";"";"4305";"Präferenz 3";"1630 17 120 (kA)";"";"[VK] Einführung in die Softwaretechnik";"2";"28.02.2022 22:30";"2";"Einführung in die Softwaretechnik (IN0006)";"Einführung in die Softwaretechnik (IN0006)";"veröffentlicht";"" -"Standardgruppe";"";"Doe";"John-James";" 01234568";"M";"john-james.doe@example.com";"Fixplatz";"28.02.2022 10:31";"";"7200";"Präferenz 3";"1630 17 121 (kA)";"";"[VK] Einführung in die Softwaretechnik";"2";"28.02.2022 10:16";"2";"Einführung in die Softwaretechnik (IN0006)";"Einführung in die Softwaretechnik (IN0006)";"veröffentlicht";"" -"Standardgruppe";"";"Doe";"Jane";" 01234569";"W";"jane.doe@example.com";"Fixplatz";"17.02.2022 19:09";"";"4200";"Präferenz 3";"1630 17 120 (kA)";"";"Freie Anmeldung";"2";"17.02.2022 09:00";"2";"Einführung in die Softwaretechnik (IN0006)";"Einführung in die Softwaretechnik (IN0006)";"veröffentlicht";"" -"Standardgruppe";"";"-";"Alice";" 01234570";"W";"alice@example.com";"Fixplatz";"04.03.2022 19:54";"";"7840";"Präferenz 3";"1630 17 121 (kA)";"";"[VK] Einführung in die Softwaretechnik";"2";"04.03.2022 19:33";"2";"Einführung in die Softwaretechnik (IN0006)";"Einführung in die Softwaretechnik (IN0006)";"veröffentlicht";"" -"Standardgruppe";"";"Ross";"Bob";" 01234571";"M";"bob.ross@example.com";"Fixplatz";"01.03.2022 21:54";"";"6040";"Präferenz 3";"1630 17 120 (kA)";"";"[VK] Einführung in die Softwaretechnik";"2";"01.03.2022 11:33";"2";"Einführung in die Softwaretechnik (IN0006)";"Einführung in die Softwaretechnik (IN0006)";"veröffentlicht";"" diff --git a/src/test/javascript/spec/helpers/sample/user-import/TUMonlineExamExport.csv b/src/test/javascript/spec/helpers/sample/user-import/TUMonlineExamExport.csv deleted file mode 100644 index cf201b75219e..000000000000 --- a/src/test/javascript/spec/helpers/sample/user-import/TUMonlineExamExport.csv +++ /dev/null @@ -1,6 +0,0 @@ -"REGISTRATION_NUMBER";"CODE_OF_STUDY_PROGRAMME";"Studienplan_Version";"FAMILY_NAME_OF_STUDENT";"FIRST_NAME_OF_STUDENT";"GESCHLECHT";"GRADE";"DATE_OF_ASSESSMENT";"REMARK";"FILE_REMARK";"ECTS_GRADE";"ANTRAGEINSICHT_BEANTRAGT";"NUMBER_OF_ATTEMPTS";"Number_Of_The_Course";"SEMESTER_OF_The_COURSE";"COURSE_TITLE";"Examiner";"Start_Time";"TERMIN_ORT";"DB_PRIMARY_Key_OF_STUDY";"DB_Primary_Key_Of_Candidate";"DB_Primary_Key_Of_Exam";"COURSE_GROUP_NAME";"TOTAL_POINTS";"TOTAL_POINTS_LIMIT_REACHED" -"01234567";"1630 17 120";"20211 ";"Mustermann";"Max Moritz";"M";"3,7";"12.04.2022";"";"";"";"";"1";"IN0001";"21W";"Einführung in die Informatik";"Musterprof Prof. Dr. rer. nat. habil. ";"14:00";"Seminarraum";"";"";"";"";"";"" -"01234568";"1630 17 121";"20211 ";"Doe";"John-James";"M";"3,7";"12.04.2022";"";"";"";"";"2";"IN0001";"21W";"Einführung in die Informatik";"Musterprof Prof. Dr. rer. nat. habil. ";"14:00";"Seminarraum";"";"";"";"";"";"" -"01234569";"1630 17 120";"20211 ";"Doe";"Jane";"W";"1,0";"12.04.2022";"";"";"";"";"1";"IN0001";"21W";"Einführung in die Informatik";"Musterprof Prof. Dr. rer. nat. habil. ";"14:00";"Seminarraum";"";"";"";"";"";"" -"01234570";"1630 17 121";"20211 ";"-";"Alice";"W";"1,3";"12.04.2022";"";"";"";"";"2";"IN0001";"21W";"Einführung in die Informatik";"Musterprof Prof. Dr. rer. nat. habil. ";"14:00";"Seminarraum";"";"";"";"";"";"" -"01234571";"1630 17 120";"20211 ";"Ross";"Bob";"M";"4,0";"12.04.2022";"";"";"";"";"1";"IN0001";"21W";"Einführung in die Informatik";"Musterprof Prof. Dr. rer. nat. habil. ";"14:00";"Seminarraum";"";"";"";"";"";"" diff --git a/src/test/javascript/spec/helpers/sample/user-import/UserImportEmailOnlySampleFile.csv b/src/test/javascript/spec/helpers/sample/user-import/UserImportEmailOnlySampleFile.csv deleted file mode 100644 index fd62ddc450b6..000000000000 --- a/src/test/javascript/spec/helpers/sample/user-import/UserImportEmailOnlySampleFile.csv +++ /dev/null @@ -1,6 +0,0 @@ -"Email" -"testuser1@mail.com" -"testuser2@mail.com" -"testuser3@mail.com" -"testuser4@mail.com" -"testuser5@mail.com" diff --git a/src/test/javascript/spec/helpers/sample/user-import/UserImportSampleFile_1.csv b/src/test/javascript/spec/helpers/sample/user-import/UserImportSampleFile_1.csv deleted file mode 100644 index 089d17fa57cc..000000000000 --- a/src/test/javascript/spec/helpers/sample/user-import/UserImportSampleFile_1.csv +++ /dev/null @@ -1,7 +0,0 @@ -Matrikel-nuMMer,Irrelevant,Login,Irrelevant2,Forename,Irrelevant3,Family_name -01234567,,,,Max Moritz,,Mustermann -01234568,,,,John-James,,Doe -01234569,,,,Jane,,Doe -01234570,,,,Alice,,- -01234571,,,,Bob,,Ross - diff --git a/src/test/javascript/spec/helpers/sample/user-import/UserImportSampleFile_2.csv b/src/test/javascript/spec/helpers/sample/user-import/UserImportSampleFile_2.csv deleted file mode 100644 index 34a265dbe0b6..000000000000 --- a/src/test/javascript/spec/helpers/sample/user-import/UserImportSampleFile_2.csv +++ /dev/null @@ -1,7 +0,0 @@ -USELESS_FIELD;MATRIKEL_NUMMER;LOGIN;VORNAME;NACHNAME -;01234567;;Max Moritz ;Mustermann -;01234568;; John-James;Doe -;01234569 ;;Jane;Doe -; 01234570;;Alice ;- -;01234571;; Bob;Ross - diff --git a/src/test/javascript/spec/helpers/sample/user-import/UserImportSampleFile_3.csv b/src/test/javascript/spec/helpers/sample/user-import/UserImportSampleFile_3.csv deleted file mode 100644 index 1412fa020c73..000000000000 --- a/src/test/javascript/spec/helpers/sample/user-import/UserImportSampleFile_3.csv +++ /dev/null @@ -1,7 +0,0 @@ -"Registration_Number","User","First_Name","Last_Name" -"01234567","","Max Moritz","Mustermann" -" 01234568","","John-James","Doe" -"01234569","","Jane ","Doe" -"01234570 ","","Alice","-" -"01234571",""," Bob","Ross" - diff --git a/src/test/javascript/spec/helpers/sample/user-import/UserImportSampleFile_4.csv b/src/test/javascript/spec/helpers/sample/user-import/UserImportSampleFile_4.csv deleted file mode 100644 index e44ce8ab7c44..000000000000 --- a/src/test/javascript/spec/helpers/sample/user-import/UserImportSampleFile_4.csv +++ /dev/null @@ -1,7 +0,0 @@ -"First Name","Last Name","Registration Number","User" -"Max Moritz","Mustermann","01234567","" -"John-James","Doe"," 01234568","" -"Jane ","Doe","01234569","" -"Alice","-","01234570 ","", -" Bob","Ross","01234571","" - diff --git a/src/test/javascript/spec/helpers/stubs/atlas/competency-card-stub.component.ts b/src/test/javascript/spec/helpers/stubs/atlas/competency-card-stub.component.ts deleted file mode 100644 index abc188288559..000000000000 --- a/src/test/javascript/spec/helpers/stubs/atlas/competency-card-stub.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component, input } from '@angular/core'; -import { CourseCompetency } from 'app/atlas/shared/entities/competency.model'; - -@Component({ selector: 'jhi-competency-card', template: '
' }) -export class CompetencyCardStubComponent { - courseId = input(); - competency = input(); - isPrerequisite = input(); - hideProgress = input(false); - noProgressRings = input(false); -} diff --git a/src/test/javascript/spec/helpers/stubs/atlas/course-description-form-stub.component.ts b/src/test/javascript/spec/helpers/stubs/atlas/course-description-form-stub.component.ts deleted file mode 100644 index a17c15470692..000000000000 --- a/src/test/javascript/spec/helpers/stubs/atlas/course-description-form-stub.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; - -@Component({ selector: 'jhi-course-description-form', template: '' }) -export class CourseDescriptionFormStubComponent { - @Input() isLoading = false; - @Output() formSubmitted: EventEmitter = new EventEmitter(); -} diff --git a/src/test/javascript/spec/helpers/stubs/atlas/knowledge-area-edit-stub.component.ts b/src/test/javascript/spec/helpers/stubs/atlas/knowledge-area-edit-stub.component.ts deleted file mode 100644 index 3da42a19dd4d..000000000000 --- a/src/test/javascript/spec/helpers/stubs/atlas/knowledge-area-edit-stub.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { KnowledgeArea, KnowledgeAreaDTO } from 'app/atlas/shared/entities/standardized-competency.model'; -import { Observable } from 'rxjs'; - -@Component({ - selector: 'jhi-knowledge-area-edit', - template: '', -}) -export class KnowledgeAreaEditStubComponent { - @Input() knowledgeAreas: KnowledgeArea[] = []; - @Input() knowledgeArea: KnowledgeAreaDTO; - @Input() isEditing = false; - @Input() dialogError: Observable; - - @Output() onSave = new EventEmitter(); - @Output() onDelete = new EventEmitter(); - @Output() onClose = new EventEmitter(); - @Output() onOpenNewCompetency = new EventEmitter(); - @Output() onOpenNewKnowledgeArea = new EventEmitter(); - @Output() isEditingChange = new EventEmitter(); -} diff --git a/src/test/javascript/spec/helpers/stubs/atlas/knowledge-area-tree-stub.component.ts b/src/test/javascript/spec/helpers/stubs/atlas/knowledge-area-tree-stub.component.ts deleted file mode 100644 index f3984609418e..000000000000 --- a/src/test/javascript/spec/helpers/stubs/atlas/knowledge-area-tree-stub.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NestedTreeControl } from '@angular/cdk/tree'; -import { Component, ContentChild, Input, TemplateRef } from '@angular/core'; -import { MatTreeNestedDataSource } from '@angular/material/tree'; -import { KnowledgeAreaForTree } from 'app/atlas/shared/entities/standardized-competency.model'; - -@Component({ - selector: 'jhi-knowledge-area-tree', - template: '', -}) -export class KnowledgeAreaTreeStubComponent { - @Input({ required: true }) dataSource: MatTreeNestedDataSource; - @Input({ required: true }) treeControl: NestedTreeControl; - - @ContentChild('knowledgeAreaTemplate') knowledgeAreaTemplate: TemplateRef; - @ContentChild('competencyTemplate') competencyTemplate: TemplateRef; -} diff --git a/src/test/javascript/spec/helpers/stubs/atlas/standardized-competency-detail-stub.component.ts b/src/test/javascript/spec/helpers/stubs/atlas/standardized-competency-detail-stub.component.ts deleted file mode 100644 index 8c5ed8812ee9..000000000000 --- a/src/test/javascript/spec/helpers/stubs/atlas/standardized-competency-detail-stub.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { StandardizedCompetencyDTO } from 'app/atlas/shared/entities/standardized-competency.model'; - -@Component({ - selector: 'jhi-standardized-competency-detail', - template: '', -}) -export class StandardizedCompetencyDetailStubComponent { - // values for the knowledge area select - @Input({ required: true }) competency: StandardizedCompetencyDTO; - @Input() knowledgeAreaTitle = ''; - @Input() sourceString = ''; - - @Output() onClose = new EventEmitter(); -} diff --git a/src/test/javascript/spec/helpers/stubs/atlas/standardized-competency-edit-stub.ts b/src/test/javascript/spec/helpers/stubs/atlas/standardized-competency-edit-stub.ts deleted file mode 100644 index 5ed16c8f40de..000000000000 --- a/src/test/javascript/spec/helpers/stubs/atlas/standardized-competency-edit-stub.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { KnowledgeArea, Source, StandardizedCompetencyDTO } from 'app/atlas/shared/entities/standardized-competency.model'; -import { Observable } from 'rxjs'; - -@Component({ - selector: 'jhi-standardized-competency-edit', - template: '', -}) -export class StandardizedCompetencyEditStubComponent { - @Input() knowledgeAreas: KnowledgeArea[] = []; - @Input() sources: Source[] = []; - @Input() competency: StandardizedCompetencyDTO; - @Input() isEditing = false; - @Input() dialogError: Observable; - - @Output() onSave = new EventEmitter(); - @Output() onDelete = new EventEmitter(); - @Output() onClose = new EventEmitter(); - @Output() isEditingChange = new EventEmitter(); -} diff --git a/src/test/javascript/spec/helpers/stubs/atlas/standardized-competency-filter-stub.component.ts b/src/test/javascript/spec/helpers/stubs/atlas/standardized-competency-filter-stub.component.ts deleted file mode 100644 index 7eaaed0d84d5..000000000000 --- a/src/test/javascript/spec/helpers/stubs/atlas/standardized-competency-filter-stub.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { KnowledgeAreaDTO } from 'app/atlas/shared/entities/standardized-competency.model'; - -@Component({ - selector: 'jhi-standardized-competency-filter', - template: '', -}) -export class StandardizedCompetencyFilterStubComponent { - @Input() competencyTitleFilter: string; - @Input() knowledgeAreaFilter?: KnowledgeAreaDTO; - @Input() knowledgeAreasForSelect: KnowledgeAreaDTO[] = []; - - @Output() competencyTitleFilterChange = new EventEmitter(); - @Output() knowledgeAreaFilterChange = new EventEmitter(); -} diff --git a/src/test/javascript/spec/helpers/stubs/exercise/unreferenced-feedback-detail-stub.component.ts b/src/test/javascript/spec/helpers/stubs/exercise/unreferenced-feedback-detail-stub.component.ts deleted file mode 100644 index b2219d8a0ee1..000000000000 --- a/src/test/javascript/spec/helpers/stubs/exercise/unreferenced-feedback-detail-stub.component.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Component, EventEmitter, Input, InputSignal, Output, input } from '@angular/core'; -import { Feedback } from 'app/assessment/shared/entities/feedback.model'; - -@Component({ - selector: 'jhi-unreferenced-feedback-detail', - template: '
', -}) -export class UnreferencedFeedbackDetailStubComponent { - @Input() public feedback: Feedback; - resultId: InputSignal = input.required(); - @Input() isSuggestion: boolean; - @Input() public readOnly: boolean; - @Input() highlightDifferences: boolean; - @Input() useDefaultFeedbackSuggestionBadgeText: boolean; - - @Output() public onFeedbackChange = new EventEmitter(); - @Output() public onFeedbackDelete = new EventEmitter(); - @Output() onAcceptSuggestion = new EventEmitter(); - @Output() onDiscardSuggestion = new EventEmitter(); - - updateFeedbackOnDrop(event: Event) { - // stop the event-bubbling, just like in the actual component - event.stopPropagation(); - } -} diff --git a/src/test/javascript/spec/helpers/stubs/shared/loading-indicator-container-stub.component.ts b/src/test/javascript/spec/helpers/stubs/shared/loading-indicator-container-stub.component.ts deleted file mode 100644 index 32de4d93157f..000000000000 --- a/src/test/javascript/spec/helpers/stubs/shared/loading-indicator-container-stub.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component, Input } from '@angular/core'; - -@Component({ selector: 'jhi-loading-indicator-container', template: '' }) -export class LoadingIndicatorContainerStubComponent { - @Input() - isLoading = false; -} diff --git a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-detail-stub.component.ts b/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-detail-stub.component.ts deleted file mode 100644 index 449e6c1198ac..000000000000 --- a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-detail-stub.component.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Component, ContentChild, Input, TemplateRef } from '@angular/core'; -import { TutorialGroup } from 'app/tutorialgroup/shared/entities/tutorial-group.model'; -import { Course } from 'app/core/course/shared/entities/course.model'; - -@Component({ - selector: 'jhi-tutorial-group-detail', - template: ` -
-
- -
-
- `, -}) -export class TutorialGroupDetailStubComponent { - @ContentChild(TemplateRef, { static: true }) header: TemplateRef; - - @Input() - tutorialGroup: TutorialGroup; - - @Input() - courseClickHandler: () => void; - - @Input() - registrationClickHandler: () => void; - - @Input() - timeZone?: string = undefined; - - @Input() - course: Course; -} diff --git a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-form-stub.component.ts b/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-form-stub.component.ts deleted file mode 100644 index 10ec8e507910..000000000000 --- a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-form-stub.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { TutorialGroupFormData } from 'app/tutorialgroup/manage/tutorial-groups/crud/tutorial-group-form/tutorial-group-form.component'; -import { Course } from 'app/core/course/shared/entities/course.model'; - -@Component({ selector: 'jhi-tutorial-group-form', template: '' }) -export class TutorialGroupFormStubComponent { - @Input() course: Course; - @Input() isEditMode = false; - @Input() formData: TutorialGroupFormData; - @Output() formSubmitted: EventEmitter = new EventEmitter(); -} diff --git a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-free-period-form-stub.component.ts b/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-free-period-form-stub.component.ts deleted file mode 100644 index ce623f4f1757..000000000000 --- a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-free-period-form-stub.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { TutorialGroupFreePeriodFormData } from 'app/tutorialgroup/manage/tutorial-free-periods/crud/tutorial-free-period-form/tutorial-group-free-period-form.component'; - -@Component({ selector: 'jhi-tutorial-free-period-form', template: '' }) -export class TutorialGroupFreePeriodFormStubComponent { - @Input() timeZone: string; - @Input() isEditMode = false; - @Input() formData: TutorialGroupFreePeriodFormData; - @Output() formSubmitted: EventEmitter = new EventEmitter(); -} diff --git a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-row-buttons-stub.component.ts b/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-row-buttons-stub.component.ts deleted file mode 100644 index caf6934aa13b..000000000000 --- a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-row-buttons-stub.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, EventEmitter, Output } from '@angular/core'; -import { Input } from '@angular/core'; -import { TutorialGroup } from 'app/tutorialgroup/shared/entities/tutorial-group.model'; -import { Course } from 'app/core/course/shared/entities/course.model'; -import { TutorialGroupSession } from 'app/tutorialgroup/shared/entities/tutorial-group-session.model'; - -@Component({ selector: 'jhi-tutorial-group-row-buttons', template: '' }) -export class TutorialGroupRowButtonsStubComponent { - @Input() isAtLeastInstructor = false; - @Input() tutorialGroup: TutorialGroup; - @Input() course: Course; - - @Output() tutorialGroupDeleted = new EventEmitter(); - @Output() registrationsChanged = new EventEmitter(); - @Output() attendanceChanged = new EventEmitter(); -} diff --git a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-session-form-stub.component.ts b/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-session-form-stub.component.ts deleted file mode 100644 index 38a2634bfb72..000000000000 --- a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-session-form-stub.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { TutorialGroupSessionFormData } from 'app/tutorialgroup/manage/tutorial-group-sessions/crud/tutorial-group-session-form/tutorial-group-session-form.component'; -import { Course } from 'app/core/course/shared/entities/course.model'; - -@Component({ selector: 'jhi-tutorial-group-session-form', template: '' }) -export class TutorialGroupSessionFormStubComponent { - @Input() timeZone: string; - @Input() isEditMode = false; - @Input() course?: Course; - @Input() formData: TutorialGroupSessionFormData; - @Output() formSubmitted: EventEmitter = new EventEmitter(); -} diff --git a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-sessions-table-stub.component.ts b/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-sessions-table-stub.component.ts deleted file mode 100644 index 3f42a053daba..000000000000 --- a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-group-sessions-table-stub.component.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Component, ContentChild, EventEmitter, Input, Output, TemplateRef } from '@angular/core'; -import { TutorialGroupSession } from 'app/tutorialgroup/shared/entities/tutorial-group-session.model'; -import { TutorialGroup } from 'app/tutorialgroup/shared/entities/tutorial-group.model'; - -@Component({ - selector: 'jhi-tutorial-group-sessions-table', - template: ` -
- @for (session of sessions; track session) { -
- } -
- `, -}) -export class TutorialGroupSessionsTableStubComponent { - @ContentChild(TemplateRef, { static: true }) extraColumn: TemplateRef; - - @Input() - sessions: TutorialGroupSession[] = []; - - @Input() - timeZone?: string = undefined; - - @Input() - showIdColumn = false; - - @Input() - tutorialGroup: TutorialGroup; - - @Input() - isReadOnly = false; - - @Output() attendanceUpdated = new EventEmitter(); -} -@Component({ - selector: '[jhi-session-row]', - template: ` -
- @if (showIdColumn) { -
- {{ session.id }} -
- } - @if (extraColumn) { -
- -
- } -
- `, -}) -export class TutorialGroupSessionRowStubComponent { - @Input() - showIdColumn = false; - - @Input() extraColumn: TemplateRef; - - @Input() session: TutorialGroupSession; - @Input() timeZone?: string = undefined; - @Input() tutorialGroup: TutorialGroup; - - @Input() - isReadOnly = false; - @Output() attendanceChanged = new EventEmitter(); -} diff --git a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-groups-configuration-form-sub.component.ts b/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-groups-configuration-form-sub.component.ts deleted file mode 100644 index e728a1d8089b..000000000000 --- a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-groups-configuration-form-sub.component.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { TutorialGroupsConfigurationFormData } from 'app/tutorialgroup/manage/tutorial-groups-configuration/crud/tutorial-groups-configuration-form/tutorial-groups-configuration-form.component'; -import { Course } from 'app/core/course/shared/entities/course.model'; - -@Component({ selector: 'jhi-tutorial-groups-configuration-form', template: '' }) -export class TutorialGroupsConfigurationFormStubComponent { - @Input() isEditMode = false; - @Input() formData: TutorialGroupsConfigurationFormData; - @Output() formSubmitted: EventEmitter = new EventEmitter(); - - @Input() - course: Course; -} diff --git a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-groups-table-stub.component.ts b/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-groups-table-stub.component.ts deleted file mode 100644 index 659a5ba8e793..000000000000 --- a/src/test/javascript/spec/helpers/stubs/tutorialgroup/tutorial-groups-table-stub.component.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Component, ContentChild, Input, TemplateRef } from '@angular/core'; -import { TutorialGroup } from 'app/tutorialgroup/shared/entities/tutorial-group.model'; -import { Course } from 'app/core/course/shared/entities/course.model'; - -@Component({ - selector: 'jhi-tutorial-groups-table', - template: ` -
- @for (tutorialGroup of tutorialGroups; track tutorialGroup) { -
- -
- } -
- `, -}) -export class TutorialGroupsTableStubComponent { - @Input() - showIdColumn = false; - - tutorialGroupsSplitAcrossMultipleCampuses = false; - mixOfOfflineAndOfflineTutorialGroups = false; - mifOfDifferentLanguages = false; - - @Input() - showChannelColumn = false; - - @Input() - tutorialGroups: TutorialGroup[] = []; - - @Input() - course: Course; - - @Input() - timeZone: string; - - @Input() - tutorialGroupClickHandler: (tutorialGroup: TutorialGroup) => void; - - @ContentChild(TemplateRef, { static: true }) extraColumn: TemplateRef; -} - -@Component({ - selector: '[jhi-tutorial-group-row]', - template: ` -
- @if (showIdColumn) { -
- {{ tutorialGroup.id }} -
- } - @if (showChannelColumn) { -
- {{ tutorialGroup?.channel?.name || '' }} -
- } - @if (extraColumn) { -
- -
- } -
- `, -}) -export class TutorialGroupRowStubComponent { - @Input() - showIdColumn = false; - - @Input() - tutorialGroupsSplitAcrossMultipleCampuses = false; - @Input() - mixOfOfflineAndOfflineTutorialGroups = false; - @Input() - mifOfDifferentLanguages = false; - - @Input() - showChannelColumn = false; - - @Input() extraColumn: TemplateRef; - - @Input() tutorialGroup: TutorialGroup; - - @Input() course: Course; - - @Input() timeZone: string; - - @Input() - tutorialGroupClickHandler: (tutorialGroup: TutorialGroup) => void; -} diff --git a/src/test/javascript/spec/helpers/tree-view/common.ts b/src/test/javascript/spec/helpers/tree-view/common.ts deleted file mode 100644 index ff164196e881..000000000000 --- a/src/test/javascript/spec/helpers/tree-view/common.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -export function createGenericTestComponent(html: string, type: { new (...args: any[]): T }): ComponentFixture { - TestBed.overrideComponent(type, { set: { template: html } }); - const fixture = TestBed.createComponent(type); - fixture.detectChanges(); - return fixture as ComponentFixture; -} diff --git a/src/test/javascript/spec/helpers/utils/general.utils.ts b/src/test/javascript/spec/helpers/utils/general.utils.ts deleted file mode 100644 index 18ee8c2072b9..000000000000 --- a/src/test/javascript/spec/helpers/utils/general.utils.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { DebugElement, OnChanges, SimpleChange, SimpleChanges } from '@angular/core'; -import { By } from '@angular/platform-browser'; - -export const getFocusedElement = (debugElement: DebugElement) => { - const focusedElement = debugElement.query(By.css(':focus')).nativeElement; - expect(focusedElement).toEqual(debugElement.nativeElement); -}; - -export const getElement = (debugElement: DebugElement, identifier: string) => { - const element = debugElement.query(By.css(identifier)); - return element ? element.nativeElement : null; -}; - -export const getElements = (debugElement: DebugElement, identifier: string) => { - const elements = debugElement.queryAll(By.css(identifier)); - return elements ? elements.map((element) => element.nativeElement) : null; -}; - -export const expectElementToBeEnabled = (element: null | any) => { - expect(element).not.toBeNull(); - expect(element.disabled).toBeFalse(); -}; - -export const expectElementToBeDisabled = (element: null | any) => { - expect(element).not.toBeNull(); - expect(element.disabled).toBeTrue(); -}; - -/** - * Construct a changes obj and trigger ngOnChanges of the provided comp. - * @param comp Angular Component that implements OnChanges - * @param changes object with data needed to construct SimpleChange objects. - */ -export const triggerChanges = (comp: OnChanges, ...changes: Array<{ property: string; currentValue: any; previousValue?: any; firstChange?: boolean }>) => { - const simpleChanges: SimpleChanges = changes.reduce((acc, { property, currentValue, previousValue, firstChange = true }) => { - return { ...acc, [property]: new SimpleChange(previousValue, currentValue, firstChange) }; - }, {}); - comp.ngOnChanges(simpleChanges); -}; diff --git a/src/test/javascript/spec/integration/code-editor/code-editor-container.integration.spec.ts b/src/test/javascript/spec/integration/code-editor/code-editor-container.integration.spec.ts deleted file mode 100644 index 9c5626c6aa71..000000000000 --- a/src/test/javascript/spec/integration/code-editor/code-editor-container.integration.spec.ts +++ /dev/null @@ -1,559 +0,0 @@ -import { ComponentFixture, TestBed, discardPeriodicTasks, fakeAsync, flush, tick } from '@angular/core/testing'; -import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; -import dayjs from 'dayjs/esm'; -import { DebugElement } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { BehaviorSubject, Subject, firstValueFrom, of } from 'rxjs'; -import { ParticipationWebsocketService } from 'app/core/course/shared/services/participation-websocket.service'; -import { ProgrammingExerciseParticipationService } from 'app/programming/manage/services/programming-exercise-participation.service'; -import { - CommitState, - DeleteFileChange, - DomainType, - EditorState, - FileBadge, - FileBadgeType, - FileType, - GitConflictState, -} from 'app/programming/shared/code-editor/model/code-editor.model'; -import { buildLogs, extractedBuildLogErrors, extractedErrorFiles } from '../../helpers/sample/build-logs'; -import { problemStatement } from '../../helpers/sample/problemStatement.json'; -import { MockProgrammingExerciseParticipationService } from '../../helpers/mocks/service/mock-programming-exercise-participation.service'; -import { ProgrammingSubmissionService, ProgrammingSubmissionState, ProgrammingSubmissionStateObj } from 'app/programming/shared/services/programming-submission.service'; -import { MockProgrammingSubmissionService } from '../../helpers/mocks/service/mock-programming-submission.service'; -import { GuidedTourService } from 'app/core/guided-tour/guided-tour.service'; -import { WebsocketService } from 'app/shared/service/websocket.service'; -import { MockWebsocketService } from '../../helpers/mocks/service/mock-websocket.service'; -import { Participation } from 'app/exercise/shared/entities/participation/participation.model'; -import { BuildLogEntryArray } from 'app/buildagent/shared/entities/build-log.model'; -import { CodeEditorConflictStateService } from 'app/programming/shared/code-editor/services/code-editor-conflict-state.service'; -import { ResultService } from 'app/exercise/result/result.service'; -import { StudentParticipation } from 'app/exercise/shared/entities/participation/student-participation.model'; -import { Result } from 'app/exercise/shared/entities/result/result.model'; -import { - CodeEditorBuildLogService, - CodeEditorRepositoryFileService, - CodeEditorRepositoryService, -} from 'app/programming/shared/code-editor/services/code-editor-repository.service'; -import { Feedback } from 'app/assessment/shared/entities/feedback.model'; -import { DomainService } from 'app/programming/shared/code-editor/services/code-editor-domain.service'; -import { ProgrammingSubmission } from 'app/programming/shared/entities/programming-submission.model'; -import { MockActivatedRouteWithSubjects } from '../../helpers/mocks/activated-route/mock-activated-route-with-subjects'; -import { MockParticipationWebsocketService } from '../../helpers/mocks/service/mock-participation-websocket.service'; -import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; -import { MockResultService } from '../../helpers/mocks/service/mock-result.service'; -import { MockCodeEditorRepositoryService } from '../../helpers/mocks/service/mock-code-editor-repository.service'; -import { MockCodeEditorRepositoryFileService } from '../../helpers/mocks/service/mock-code-editor-repository-file.service'; -import { MockCodeEditorBuildLogService } from '../../helpers/mocks/service/mock-code-editor-build-log.service'; -import { CodeEditorContainerComponent } from 'app/programming/manage/code-editor/container/code-editor-container.component'; -import { omit } from 'lodash-es'; -import { ProgrammingLanguage, ProjectType } from 'app/programming/shared/entities/programming-exercise.model'; -import { MockComponent, MockProvider } from 'ng-mocks'; -import { CodeEditorHeaderComponent } from 'app/programming/manage/code-editor/header/code-editor-header.component'; -import { AlertService } from 'app/shared/service/alert.service'; -import { MockResizeObserver } from '../../helpers/mocks/service/mock-resize-observer'; -import { CodeEditorMonacoComponent } from 'app/programming/shared/code-editor/monaco/code-editor-monaco.component'; -import { MonacoEditorComponent } from '../../../../../main/webapp/app/shared/monaco-editor/monaco-editor.component'; -import { MockTranslateService } from '../../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 { provideHttpClientTesting } from '@angular/common/http/testing'; -import { provideHttpClient } from '@angular/common/http'; -import { GuidedTourMapping } from 'app/core/guided-tour/guided-tour-setting.model'; - -describe('CodeEditorContainerIntegration', () => { - let container: CodeEditorContainerComponent; - let containerFixture: ComponentFixture; - let containerDebugElement: DebugElement; - let conflictService: CodeEditorConflictStateService; - let domainService: DomainService; - let checkIfRepositoryIsCleanStub: jest.SpyInstance; - let getRepositoryContentStub: jest.SpyInstance; - let subscribeForLatestResultOfParticipationStub: jest.SpyInstance; - let getFeedbackDetailsForResultStub: jest.SpyInstance; - let getBuildLogsStub: jest.SpyInstance; - let getFileStub: jest.SpyInstance; - let saveFilesStub: jest.SpyInstance; - let commitStub: jest.SpyInstance; - let getStudentParticipationWithLatestResultStub: jest.SpyInstance; - let getLatestPendingSubmissionStub: jest.SpyInstance; - let guidedTourService: GuidedTourService; - let subscribeForLatestResultOfParticipationSubject: BehaviorSubject; - let getLatestPendingSubmissionSubject = new Subject(); - - const result = { id: 3, successful: false, completionDate: dayjs().subtract(2, 'days') }; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - providers: [ - CodeEditorConflictStateService, - MockProvider(AlertService), - { provide: ActivatedRoute, useClass: MockActivatedRouteWithSubjects }, - { provide: WebsocketService, useClass: MockWebsocketService }, - { provide: ParticipationWebsocketService, useClass: MockParticipationWebsocketService }, - { provide: ProgrammingExerciseParticipationService, useClass: MockProgrammingExerciseParticipationService }, - { provide: SessionStorageService, useClass: MockSyncStorage }, - { provide: ResultService, useClass: MockResultService }, - { provide: LocalStorageService, useClass: MockSyncStorage }, - { provide: CodeEditorRepositoryService, useClass: MockCodeEditorRepositoryService }, - { provide: CodeEditorRepositoryFileService, useClass: MockCodeEditorRepositoryFileService }, - { provide: CodeEditorBuildLogService, useClass: MockCodeEditorBuildLogService }, - { provide: ResultService, useClass: MockResultService }, - { provide: ProgrammingSubmissionService, useClass: MockProgrammingSubmissionService }, - { provide: TranslateService, useClass: MockTranslateService }, - { provide: ProfileService, useClass: MockProfileService }, - provideHttpClient(), - provideHttpClientTesting(), - ], - }) - .overrideComponent(CodeEditorMonacoComponent, { set: { imports: [MonacoEditorComponent, MockComponent(CodeEditorHeaderComponent)] } }) - .compileComponents(); - - containerFixture = TestBed.createComponent(CodeEditorContainerComponent); - container = containerFixture.componentInstance; - containerDebugElement = containerFixture.debugElement; - guidedTourService = TestBed.inject(GuidedTourService); - - const codeEditorRepositoryService = containerDebugElement.injector.get(CodeEditorRepositoryService); - const codeEditorRepositoryFileService = containerDebugElement.injector.get(CodeEditorRepositoryFileService); - const participationWebsocketService = containerDebugElement.injector.get(ParticipationWebsocketService); - const resultService = containerDebugElement.injector.get(ResultService); - const buildLogService = containerDebugElement.injector.get(CodeEditorBuildLogService); - const programmingExerciseParticipationService = containerDebugElement.injector.get(ProgrammingExerciseParticipationService); - conflictService = containerDebugElement.injector.get(CodeEditorConflictStateService); - domainService = containerDebugElement.injector.get(DomainService); - const submissionService = containerDebugElement.injector.get(ProgrammingSubmissionService); - - subscribeForLatestResultOfParticipationSubject = new BehaviorSubject(undefined); - - getLatestPendingSubmissionSubject = new Subject(); - - checkIfRepositoryIsCleanStub = jest.spyOn(codeEditorRepositoryService, 'getStatus'); - getRepositoryContentStub = jest.spyOn(codeEditorRepositoryFileService, 'getRepositoryContent'); - subscribeForLatestResultOfParticipationStub = jest - .spyOn(participationWebsocketService, 'subscribeForLatestResultOfParticipation') - .mockReturnValue(subscribeForLatestResultOfParticipationSubject); - getFeedbackDetailsForResultStub = jest.spyOn(resultService, 'getFeedbackDetailsForResult'); - getBuildLogsStub = jest.spyOn(buildLogService, 'getBuildLogs'); - getFileStub = jest.spyOn(codeEditorRepositoryFileService, 'getFile'); - saveFilesStub = jest.spyOn(codeEditorRepositoryFileService, 'updateFiles'); - commitStub = jest.spyOn(codeEditorRepositoryService, 'commit'); - getStudentParticipationWithLatestResultStub = jest.spyOn(programmingExerciseParticipationService, 'getStudentParticipationWithLatestResult'); - getLatestPendingSubmissionStub = jest.spyOn(submissionService, 'getLatestPendingSubmissionByParticipationId').mockReturnValue(getLatestPendingSubmissionSubject); - // Mock the ResizeObserver, which is not available in the test environment - global.ResizeObserver = jest.fn().mockImplementation((callback: ResizeObserverCallback) => { - return new MockResizeObserver(callback); - }); - }); - - afterEach(() => { - jest.restoreAllMocks(); - subscribeForLatestResultOfParticipationSubject = new BehaviorSubject(undefined); - subscribeForLatestResultOfParticipationStub.mockReturnValue(subscribeForLatestResultOfParticipationSubject); - - getLatestPendingSubmissionSubject = new Subject(); - getLatestPendingSubmissionStub.mockReturnValue(getLatestPendingSubmissionSubject); - }); - - const cleanInitialize = () => { - const exercise = { id: 1, problemStatement }; - const participation = { id: 2, exercise, student: { id: 99 }, results: [result] } as StudentParticipation; - const isCleanSubject = new Subject(); - const getRepositoryContentSubject = new Subject(); - const getBuildLogsSubject = new Subject(); - checkIfRepositoryIsCleanStub.mockReturnValue(isCleanSubject); - getRepositoryContentStub.mockReturnValue(getRepositoryContentSubject); - getFeedbackDetailsForResultStub.mockReturnValue(of([])); - getBuildLogsStub.mockReturnValue(getBuildLogsSubject); - getLatestPendingSubmissionStub.mockReturnValue(getLatestPendingSubmissionSubject); - - container.participation = participation as any; - - // TODO: This should be replaced by testing with route params. - domainService.setDomain([DomainType.PARTICIPATION, participation]); - containerFixture.detectChanges(); - - container.commitState = CommitState.UNDEFINED; - - isCleanSubject.next({ repositoryStatus: CommitState.CLEAN }); - getBuildLogsSubject.next(buildLogs); - getRepositoryContentSubject.next({ file: FileType.FILE, folder: FileType.FOLDER, file2: FileType.FILE }); - getLatestPendingSubmissionSubject.next({ participationId: 1, submissionState: ProgrammingSubmissionState.HAS_NO_PENDING_SUBMISSION, submission: undefined }); - - containerFixture.detectChanges(); - - // container - expect(container.commitState).toBe(CommitState.CLEAN); - expect(container.editorState).toBe(EditorState.CLEAN); - expect(container.buildOutput.isBuilding).toBeFalse(); - expect(container.unsavedFiles).toStrictEqual({}); - - // file browser - expect(checkIfRepositoryIsCleanStub).toHaveBeenCalledOnce(); - expect(getRepositoryContentStub).toHaveBeenCalledOnce(); - expect(container.fileBrowser.errorFiles).toEqual(extractedErrorFiles); - expect(container.fileBrowser.unsavedFiles).toHaveLength(0); - - // monaco editor - expect(container.monacoEditor.loadingCount()).toBe(0); - expect(container.monacoEditor.commitState()).toBe(CommitState.CLEAN); - - // actions - expect(container.actions.commitState).toBe(CommitState.CLEAN); - expect(container.actions.editorState).toBe(EditorState.CLEAN); - expect(container.actions.isBuilding).toBeFalse(); - - // status - expect(container.fileBrowser.status.commitState).toBe(CommitState.CLEAN); - expect(container.fileBrowser.status.editorState).toBe(EditorState.CLEAN); - - // build output - expect(getBuildLogsStub).toHaveBeenCalledOnce(); - expect(container.buildOutput.rawBuildLogs.extractErrors(ProgrammingLanguage.JAVA, ProjectType.PLAIN_MAVEN)).toEqual(extractedBuildLogErrors); - expect(container.buildOutput.isBuilding).toBeFalse(); - - // instructions - expect(container.instructions).toBeDefined(); // Have to use this as it's a component - - // called by build output - expect(getFeedbackDetailsForResultStub).toHaveBeenCalledOnce(); - expect(getFeedbackDetailsForResultStub).toHaveBeenCalledWith(participation.id!, participation.results![0]); - }; - - const loadFile = async (fileName: string, fileContent: string) => { - getFileStub.mockReturnValue(of({ fileContent })); - container.fileBrowser.selectedFile = fileName; - await container.monacoEditor.selectFileInEditor(fileName); - }; - - it('should initialize all components correctly if all server calls are successful', fakeAsync(() => { - cleanInitialize(); - flush(); - discardPeriodicTasks(); - expect(subscribeForLatestResultOfParticipationStub).toHaveBeenCalledOnce(); - })); - - it('should not load files and render other components correctly if the repository status cannot be retrieved', fakeAsync(() => { - const exercise = { id: 1, problemStatement, course: { id: 2 } }; - const participation = { id: 2, exercise, results: [result] } as StudentParticipation; - const isCleanSubject = new Subject(); - const getBuildLogsSubject = new Subject(); - checkIfRepositoryIsCleanStub.mockReturnValue(isCleanSubject); - subscribeForLatestResultOfParticipationStub.mockReturnValue(of(undefined)); - getFeedbackDetailsForResultStub.mockReturnValue(of([])); - getBuildLogsStub.mockReturnValue(getBuildLogsSubject); - - container.participation = participation; - - // TODO: This should be replaced by testing with route params. - domainService.setDomain([DomainType.PARTICIPATION, participation]); - containerFixture.detectChanges(); - - container.commitState = CommitState.UNDEFINED; - - isCleanSubject.error('fatal error'); - getBuildLogsSubject.next(buildLogs); - getLatestPendingSubmissionSubject.next({ participationId: 1, submissionState: ProgrammingSubmissionState.HAS_FAILED_SUBMISSION, submission: undefined }); - - containerFixture.detectChanges(); - - // container - expect(container.commitState).toBe(CommitState.COULD_NOT_BE_RETRIEVED); - expect(container.editorState).toBe(EditorState.CLEAN); - expect(container.buildOutput.isBuilding).toBeFalse(); - expect(container.unsavedFiles).toStrictEqual({}); - - // file browser - expect(checkIfRepositoryIsCleanStub).toHaveBeenCalledOnce(); - expect(getRepositoryContentStub).not.toHaveBeenCalled(); - expect(container.fileBrowser.errorFiles).toEqual(extractedErrorFiles); - expect(container.fileBrowser.unsavedFiles).toHaveLength(0); - - // monaco editor - expect(container.monacoEditor.loadingCount()).toBe(0); - expect(container.monacoEditor.annotationsArray?.map((a) => omit(a, 'hash'))).toEqual(extractedBuildLogErrors); - expect(container.monacoEditor.commitState()).toBe(CommitState.COULD_NOT_BE_RETRIEVED); - - // actions - expect(container.actions.commitState).toBe(CommitState.COULD_NOT_BE_RETRIEVED); - expect(container.actions.editorState).toBe(EditorState.CLEAN); - expect(container.actions.isBuilding).toBeFalse(); - - // status - expect(container.fileBrowser.status.commitState).toBe(CommitState.COULD_NOT_BE_RETRIEVED); - expect(container.fileBrowser.status.editorState).toBe(EditorState.CLEAN); - - // build output - expect(getBuildLogsStub).toHaveBeenCalledOnce(); - expect(container.buildOutput.rawBuildLogs.extractErrors(ProgrammingLanguage.JAVA, ProjectType.PLAIN_MAVEN)).toEqual(extractedBuildLogErrors); - expect(container.buildOutput.isBuilding).toBeFalse(); - - // instructions - expect(container.instructions).toBeDefined(); // Have to use this as it's a component - - // called by build output & instructions - expect(getFeedbackDetailsForResultStub).toHaveBeenCalledOnce(); - expect(getFeedbackDetailsForResultStub).toHaveBeenCalledWith(participation.id!, participation.results![0]); - - flush(); - discardPeriodicTasks(); - expect(subscribeForLatestResultOfParticipationStub).toHaveBeenCalledOnce(); - })); - - it('should update the file browser and monaco editor on file selection', async () => { - cleanInitialize(); - const selectedFile = Object.keys(container.fileBrowser.repositoryFiles)[0]; - const fileContent = 'lorem ipsum'; - await loadFile(selectedFile, fileContent); - - containerFixture.detectChanges(); - expect(container.selectedFile).toBe(selectedFile); - expect(container.monacoEditor.selectedFile()).toBe(selectedFile); - expect(container.monacoEditor.loadingCount()).toBe(0); - expect(container.monacoEditor.fileSession()).toContainKey(selectedFile); - expect(getFileStub).toHaveBeenCalledOnce(); - expect(getFileStub).toHaveBeenCalledWith(selectedFile); - - containerFixture.detectChanges(); - expect(container.getText()).toBe(fileContent); - }); - - it('should mark file to have unsaved changes in file tree if the file was changed in editor', async () => { - cleanInitialize(); - const selectedFile = Object.keys(container.fileBrowser.repositoryFiles)[0]; - const fileContent = 'lorem ipsum'; - const newFileContent = 'new lorem ipsum'; - await loadFile(selectedFile, fileContent); - - containerFixture.detectChanges(); - container.monacoEditor.onFileTextChanged(newFileContent); - containerFixture.detectChanges(); - - expect(getFileStub).toHaveBeenCalledOnce(); - expect(getFileStub).toHaveBeenCalledWith(selectedFile); - expect(container.unsavedFiles).toEqual({ [selectedFile]: newFileContent }); - expect(container.fileBrowser.unsavedFiles).toEqual([selectedFile]); - expect(container.editorState).toBe(EditorState.UNSAVED_CHANGES); - expect(container.actions.editorState).toBe(EditorState.UNSAVED_CHANGES); - }); - - it('should save files and remove unsaved status of saved files afterwards', async () => { - // setup - cleanInitialize(); - const selectedFile = Object.keys(container.fileBrowser.repositoryFiles)[0]; - const otherFileWithUnsavedChanges = Object.keys(container.fileBrowser.repositoryFiles)[2]; - const fileContent = 'lorem ipsum'; - const newFileContent = 'new lorem ipsum'; - const saveFilesSubject = new Subject(); - saveFilesStub.mockReturnValue(saveFilesSubject); - container.unsavedFiles = { [otherFileWithUnsavedChanges]: 'lorem ipsum dolet', [selectedFile]: newFileContent }; - await loadFile(selectedFile, fileContent); - containerFixture.detectChanges(); - - // init saving - container.actions.saveChangedFiles().subscribe(); - expect(container.commitState).toBe(CommitState.UNCOMMITTED_CHANGES); - expect(container.editorState).toBe(EditorState.SAVING); - - // emit saving result - saveFilesSubject.next({ [selectedFile]: undefined, [otherFileWithUnsavedChanges]: undefined }); - containerFixture.detectChanges(); - - // check if saving result updates comps as expected - expect(container.unsavedFiles).toStrictEqual({}); - expect(container.editorState).toBe(EditorState.CLEAN); - expect(container.commitState).toBe(CommitState.UNCOMMITTED_CHANGES); - expect(container.fileBrowser.unsavedFiles).toHaveLength(0); - expect(container.actions.editorState).toBe(EditorState.CLEAN); - }); - - it('should remove the unsaved changes flag in all components if the unsaved file is deleted', () => { - cleanInitialize(); - const repositoryFiles = { file: FileType.FILE, file2: FileType.FILE, folder: FileType.FOLDER }; - const expectedFilesAfterDelete = { file2: FileType.FILE, folder: FileType.FOLDER }; - const unsavedChanges = { file: 'lorem ipsum' }; - container.fileBrowser.repositoryFiles = repositoryFiles; - container.unsavedFiles = unsavedChanges; - - containerFixture.detectChanges(); - - expect(container.fileBrowser.unsavedFiles).toEqual(Object.keys(unsavedChanges)); - expect(container.actions.editorState).toBe(EditorState.UNSAVED_CHANGES); - - container.fileBrowser.onFileDeleted(new DeleteFileChange(FileType.FILE, 'file')); - containerFixture.detectChanges(); - expect(container.unsavedFiles).toStrictEqual({}); - expect(container.fileBrowser.repositoryFiles).toEqual(expectedFilesAfterDelete); - expect(container.actions.editorState).toBe(EditorState.CLEAN); - }); - - it('should wait for build result after submission if no unsaved changes exist', () => { - cleanInitialize(); - const successfulSubmission = { id: 1, buildFailed: false } as ProgrammingSubmission; - const successfulResult = { id: 4, successful: true, feedbacks: [] as Feedback[], participation: { id: 3 } } as Result; - successfulResult.submission = successfulSubmission; - const expectedBuildLog = new BuildLogEntryArray(); - expect(container.unsavedFiles).toStrictEqual({}); - container.commitState = CommitState.UNCOMMITTED_CHANGES; - containerFixture.detectChanges(); - - // commit - expect(container.actions.commitState).toBe(CommitState.UNCOMMITTED_CHANGES); - commitStub.mockReturnValue(of(undefined)); - getLatestPendingSubmissionSubject.next({ - submissionState: ProgrammingSubmissionState.IS_BUILDING_PENDING_SUBMISSION, - submission: {} as ProgrammingSubmission, - participationId: successfulResult!.participation!.id!, - }); - container.actions.commit(); - containerFixture.detectChanges(); - - // waiting for build successfulResult - expect(container.commitState).toBe(CommitState.CLEAN); - expect(container.buildOutput.isBuilding).toBeTrue(); - - getLatestPendingSubmissionSubject.next({ - submissionState: ProgrammingSubmissionState.HAS_NO_PENDING_SUBMISSION, - submission: undefined, - participationId: successfulResult!.participation!.id!, - }); - subscribeForLatestResultOfParticipationSubject.next(successfulResult); - containerFixture.detectChanges(); - - expect(container.buildOutput.isBuilding).toBeFalse(); - expect(container.buildOutput.rawBuildLogs).toEqual(expectedBuildLog); - expect(container.fileBrowser.errorFiles).toHaveLength(0); - }); - - it('should first save unsaved files before triggering commit', async () => { - cleanInitialize(); - const successfulSubmission = { id: 1, buildFailed: false } as ProgrammingSubmission; - const successfulResult = { id: 4, successful: true, feedbacks: [] as Feedback[], participation: { id: 3 } } as Result; - successfulResult.submission = successfulSubmission; - const expectedBuildLog = new BuildLogEntryArray(); - const unsavedFile = Object.keys(container.fileBrowser.repositoryFiles)[0]; - const saveFilesSubject = new Subject(); - saveFilesStub.mockReturnValue(saveFilesSubject); - container.unsavedFiles = { [unsavedFile]: 'lorem ipsum' }; - container.editorState = EditorState.UNSAVED_CHANGES; - container.commitState = CommitState.UNCOMMITTED_CHANGES; - containerFixture.detectChanges(); - - // trying to commit - container.actions.commit(); - containerFixture.detectChanges(); - - // saving before commit - expect(saveFilesStub).toHaveBeenCalledOnce(); - expect(saveFilesStub).toHaveBeenCalledWith([{ fileName: unsavedFile, fileContent: 'lorem ipsum' }], true); - expect(container.editorState).toBe(EditorState.SAVING); - expect(container.fileBrowser.status.editorState).toBe(EditorState.SAVING); - // committing - expect(commitStub).not.toHaveBeenCalled(); - expect(container.commitState).toBe(CommitState.COMMITTING); - expect(container.fileBrowser.status.commitState).toBe(CommitState.COMMITTING); - saveFilesSubject.next({ [unsavedFile]: undefined }); - - expect(container.editorState).toBe(EditorState.CLEAN); - subscribeForLatestResultOfParticipationSubject.next(successfulResult); - getLatestPendingSubmissionSubject.next({ - submissionState: ProgrammingSubmissionState.IS_BUILDING_PENDING_SUBMISSION, - submission: {} as ProgrammingSubmission, - participationId: successfulResult!.participation!.id!, - }); - - // Commit state should change asynchronously - containerFixture.detectChanges(); - await firstValueFrom(container.actions.commitStateChange); - - // waiting for build result - expect(container.commitState).toBe(CommitState.CLEAN); - expect(container.buildOutput.isBuilding).toBeTrue(); - - getLatestPendingSubmissionSubject.next({ - submissionState: ProgrammingSubmissionState.HAS_NO_PENDING_SUBMISSION, - submission: undefined, - participationId: successfulResult!.participation!.id!, - }); - containerFixture.detectChanges(); - - expect(container.buildOutput.isBuilding).toBeFalse(); - expect(container.buildOutput.rawBuildLogs).toEqual(expectedBuildLog); - expect(container.fileBrowser.errorFiles).toHaveLength(0); - - containerFixture.destroy(); - }); - - it('should enter conflict mode if a git conflict between local and remote arises', fakeAsync(() => { - const guidedTourMapping = {} as GuidedTourMapping; - jest.spyOn(guidedTourService, 'checkTourState').mockReturnValue(true); - guidedTourService.guidedTourMapping = guidedTourMapping; - - const successfulResult = { id: 3, successful: false }; - const participation = { id: 1, results: [successfulResult], exercise: { id: 99 } } as StudentParticipation; - const feedbacks = [{ id: 2 }] as Feedback[]; - const findWithLatestResultSubject = new Subject(); - const isCleanSubject = new Subject(); - getStudentParticipationWithLatestResultStub.mockReturnValue(findWithLatestResultSubject); - checkIfRepositoryIsCleanStub.mockReturnValue(isCleanSubject); - getFeedbackDetailsForResultStub.mockReturnValue(of(feedbacks)); - getRepositoryContentStub.mockReturnValue(of([])); - - container.participation = participation; - domainService.setDomain([DomainType.PARTICIPATION, participation]); - - containerFixture.detectChanges(); - - findWithLatestResultSubject.next(participation); - - containerFixture.detectChanges(); - - // Create conflict. - isCleanSubject.next({ repositoryStatus: CommitState.CONFLICT }); - containerFixture.detectChanges(); - - expect(container.commitState).toBe(CommitState.CONFLICT); - expect(getRepositoryContentStub).not.toHaveBeenCalled(); - - // Resolve conflict. - conflictService.notifyConflictState(GitConflictState.OK); - tick(); - containerFixture.detectChanges(); - isCleanSubject.next({ repositoryStatus: CommitState.CLEAN }); - containerFixture.detectChanges(); - - expect(container.commitState).toBe(CommitState.CLEAN); - expect(getRepositoryContentStub).toHaveBeenCalledOnce(); - - containerFixture.destroy(); - flush(); - })); - - it.each([ - ['loadingFailed', 'artemisApp.editor.errors.loadingFailed', { connectionIssue: '' }], - ['loadingFailedInternetDisconnected', 'artemisApp.editor.errors.loadingFailed', { connectionIssue: 'artemisApp.editor.errors.InternetDisconnected' }], - ])('onError should handle disconnectedInternet', (error: string, errorKey: string, translationParams: { connectionIssue: string }) => { - const alertService = TestBed.inject(AlertService); - const alertServiceSpy = jest.spyOn(alertService, 'error'); - container.onError(error); - expect(alertServiceSpy).toHaveBeenCalledWith(errorKey, translationParams); - }); - - it('should create file badges for feedback suggestions', () => { - container.feedbackSuggestions = [ - { reference: 'file:src/Test1.java_line:2' }, - { reference: 'file:src/Test2.java_line:2' }, - { reference: 'file:src/Test2.java_line:4' }, - { reference: 'file:src/Test3.java_line:4' }, - { reference: 'file:src/Test3.java_line:10' }, - { reference: 'file:src/Test3.java_line:11' }, - ]; - container.updateFileBadges(); - expect(container.fileBadges).toEqual({ - 'src/Test1.java': [new FileBadge(FileBadgeType.FEEDBACK_SUGGESTION, 1)], - 'src/Test2.java': [new FileBadge(FileBadgeType.FEEDBACK_SUGGESTION, 2)], - 'src/Test3.java': [new FileBadge(FileBadgeType.FEEDBACK_SUGGESTION, 3)], - }); - }); -}); diff --git a/src/test/javascript/spec/integration/code-editor/code-editor-instructor.integration.spec.ts b/src/test/javascript/spec/integration/code-editor/code-editor-instructor.integration.spec.ts deleted file mode 100644 index 321f0bf73ed7..000000000000 --- a/src/test/javascript/spec/integration/code-editor/code-editor-instructor.integration.spec.ts +++ /dev/null @@ -1,496 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; -import { TranslateModule } from '@ngx-translate/core'; -import { JhiLanguageHelper } from 'app/core/language/shared/language.helper'; -import { AccountService } from 'app/core/auth/account.service'; -import { DebugElement } from '@angular/core'; -import { ActivatedRoute, Params, Router } from '@angular/router'; -import { BehaviorSubject, of, Subject, throwError } from 'rxjs'; -import { ProgrammingExerciseParticipationService } from 'app/programming/manage/services/programming-exercise-participation.service'; -import { ProgrammingExerciseService } from 'app/programming/manage/services/programming-exercise.service'; -import { DomainType, FileType, RepositoryType } from 'app/programming/shared/code-editor/model/code-editor.model'; -import { MockAccountService } from '../../helpers/mocks/service/mock-account.service'; -import { MockRouter } from '../../helpers/mocks/mock-router'; -import { problemStatement } from '../../helpers/sample/problemStatement.json'; -import { MockProgrammingExerciseParticipationService } from '../../helpers/mocks/service/mock-programming-exercise-participation.service'; -import { CodeEditorInstructorAndEditorContainerComponent } from 'app/programming/manage/code-editor/instructor-and-editor-container/code-editor-instructor-and-editor-container.component'; -import { ParticipationWebsocketService } from 'app/core/course/shared/services/participation-websocket.service'; -import { MockCourseExerciseService } from '../../helpers/mocks/service/mock-course-exercise.service'; -import { - CodeEditorBuildLogService, - CodeEditorRepositoryFileService, - CodeEditorRepositoryService, -} from 'app/programming/shared/code-editor/services/code-editor-repository.service'; -import { ResultService } from 'app/exercise/result/result.service'; -import { DomainService } from 'app/programming/shared/code-editor/services/code-editor-domain.service'; -import { TemplateProgrammingExerciseParticipation } from 'app/exercise/shared/entities/participation/template-programming-exercise-participation.model'; -import { Result } from 'app/exercise/shared/entities/result/result.model'; -import { ParticipationService } from 'app/exercise/participation/participation.service'; -import { ProgrammingExercise } from 'app/programming/shared/entities/programming-exercise.model'; -import { ProgrammingExerciseStudentParticipation } from 'app/exercise/shared/entities/participation/programming-exercise-student-participation.model'; -import { SolutionProgrammingExerciseParticipation } from 'app/exercise/shared/entities/participation/solution-programming-exercise-participation.model'; -import { MockActivatedRouteWithSubjects } from '../../helpers/mocks/activated-route/mock-activated-route-with-subjects'; -import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; -import { MockResultService } from '../../helpers/mocks/service/mock-result.service'; -import { MockCodeEditorRepositoryService } from '../../helpers/mocks/service/mock-code-editor-repository.service'; -import { MockCodeEditorBuildLogService } from '../../helpers/mocks/service/mock-code-editor-build-log.service'; -import { MockCodeEditorRepositoryFileService } from '../../helpers/mocks/service/mock-code-editor-repository-file.service'; -import { MockParticipationWebsocketService } from '../../helpers/mocks/service/mock-participation-websocket.service'; -import { MockParticipationService } from '../../helpers/mocks/service/mock-participation.service'; -import { MockProgrammingExerciseService } from '../../helpers/mocks/service/mock-programming-exercise.service'; -import { WebsocketService } from 'app/shared/service/websocket.service'; -import { MockWebsocketService } from '../../helpers/mocks/service/mock-websocket.service'; -import { MockComponent, MockModule, MockPipe, MockProvider } from 'ng-mocks'; -import { CodeEditorContainerComponent } from 'app/programming/manage/code-editor/container/code-editor-container.component'; -import { IncludedInScoreBadgeComponent } from 'app/exercise/exercise-headers/included-in-score-badge/included-in-score-badge.component'; -import { ProgrammingExerciseInstructorExerciseStatusComponent } from 'app/programming/manage/status/programming-exercise-instructor-exercise-status.component'; -import { UpdatingResultComponent } from 'app/exercise/result/updating-result/updating-result.component'; -import { ProgrammingExerciseStudentTriggerBuildButtonComponent } from 'app/programming/shared/actions/trigger-build-button/student/programming-exercise-student-trigger-build-button.component'; -import { ProgrammingExerciseEditableInstructionComponent } from 'app/programming/manage/instructions-editor/programming-exercise-editable-instruction.component'; -import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; -import { CodeEditorGridComponent } from 'app/programming/shared/code-editor/layout/code-editor-grid/code-editor-grid.component'; -import { CodeEditorActionsComponent } from 'app/programming/shared/code-editor/actions/code-editor-actions.component'; -import { CodeEditorFileBrowserComponent } from 'app/programming/manage/code-editor/file-browser/code-editor-file-browser.component'; -import { CodeEditorBuildOutputComponent } from 'app/programming/manage/code-editor/build-output/code-editor-build-output.component'; -import { KeysPipe } from 'app/shared/pipes/keys.pipe'; -import { CodeEditorInstructionsComponent } from 'app/programming/shared/code-editor/instructions/code-editor-instructions.component'; -import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; -import { ProgrammingExerciseInstructionComponent } from 'app/programming/shared/instructions-render/programming-exercise-instruction.component'; -import { ProgrammingExerciseInstructionAnalysisComponent } from 'app/programming/manage/instructions-editor/analysis/programming-exercise-instruction-analysis.component'; -import { ResultComponent } from 'app/exercise/result/result.component'; -import { ProgrammingExerciseInstructionStepWizardComponent } from 'app/programming/shared/instructions-render/step-wizard/programming-exercise-instruction-step-wizard.component'; -import { ProgrammingExerciseInstructionTaskStatusComponent } from 'app/programming/shared/instructions-render/task/programming-exercise-instruction-task-status.component'; -import { CourseExerciseService } from 'app/exercise/course-exercises/course-exercise.service'; -import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; -import { CodeEditorMonacoComponent } from 'app/programming/shared/code-editor/monaco/code-editor-monaco.component'; -import { MarkdownEditorMonacoComponent } from 'app/shared/markdown-editor/monaco/markdown-editor-monaco.component'; -import { mockCodeEditorMonacoViewChildren } from '../../helpers/mocks/mock-instance.helper'; -import { ProfileService } from 'app/core/layouts/profiles/shared/profile.service'; -import { provideHttpClientTesting } from '@angular/common/http/testing'; -import { provideHttpClient } from '@angular/common/http'; -import { ProfileInfo } from 'app/core/layouts/profiles/profile-info.model'; - -describe('CodeEditorInstructorIntegration', () => { - let comp: CodeEditorInstructorAndEditorContainerComponent; - let containerFixture: ComponentFixture; - let containerDebugElement: DebugElement; - let domainService: DomainService; - let route: ActivatedRoute; - - let checkIfRepositoryIsCleanStub: jest.SpyInstance; - let getRepositoryContentStub: jest.SpyInstance; - let subscribeForLatestResultOfParticipationStub: jest.SpyInstance; - let getFeedbackDetailsForResultStub: jest.SpyInstance; - let getBuildLogsStub: jest.SpyInstance; - let findWithParticipationsStub: jest.SpyInstance; - let getLatestResultWithFeedbacksStub: jest.SpyInstance; - let navigateSpy: jest.SpyInstance; - - let checkIfRepositoryIsCleanSubject: Subject<{ isClean: boolean }>; - let getRepositoryContentSubject: Subject<{ [fileName: string]: FileType }>; - let subscribeForLatestResultOfParticipationSubject: BehaviorSubject; - let findWithParticipationsSubject: Subject<{ body: ProgrammingExercise }>; - let routeSubject: Subject; - - const mockProfileInfo = { activeProfiles: ['iris'] } as ProfileInfo; - - // Workaround for an error with MockComponent(). You can remove this once https://github.com/help-me-mom/ng-mocks/issues/8634 is resolved. - mockCodeEditorMonacoViewChildren(); - - beforeEach(() => { - return TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), MockModule(NgbTooltipModule)], - declarations: [ - CodeEditorInstructorAndEditorContainerComponent, - CodeEditorContainerComponent, - KeysPipe, - CodeEditorInstructionsComponent, - MockComponent(CodeEditorGridComponent), - MockComponent(CodeEditorActionsComponent), - MockComponent(CodeEditorFileBrowserComponent), - MockComponent(CodeEditorMonacoComponent), - CodeEditorBuildOutputComponent, - MockPipe(ArtemisDatePipe), - MockComponent(IncludedInScoreBadgeComponent), - ProgrammingExerciseInstructorExerciseStatusComponent, - UpdatingResultComponent, - MockComponent(ProgrammingExerciseStudentTriggerBuildButtonComponent), - ProgrammingExerciseEditableInstructionComponent, - MockComponent(MarkdownEditorMonacoComponent), - ProgrammingExerciseInstructionComponent, - MockComponent(ProgrammingExerciseInstructionAnalysisComponent), - MockPipe(ArtemisTranslatePipe), - MockComponent(ResultComponent), - MockComponent(ProgrammingExerciseInstructionStepWizardComponent), - MockComponent(ProgrammingExerciseInstructionTaskStatusComponent), - ], - providers: [ - JhiLanguageHelper, - { provide: Router, useClass: MockRouter }, - { provide: AccountService, useClass: MockAccountService }, - { provide: ActivatedRoute, useClass: MockActivatedRouteWithSubjects }, - { provide: SessionStorageService, useClass: MockSyncStorage }, - { provide: ResultService, useClass: MockResultService }, - { provide: LocalStorageService, useClass: MockSyncStorage }, - { provide: CourseExerciseService, useClass: MockCourseExerciseService }, - { provide: CodeEditorRepositoryService, useClass: MockCodeEditorRepositoryService }, - { provide: CodeEditorRepositoryFileService, useClass: MockCodeEditorRepositoryFileService }, - { provide: CodeEditorBuildLogService, useClass: MockCodeEditorBuildLogService }, - { provide: ParticipationWebsocketService, useClass: MockParticipationWebsocketService }, - { provide: ResultService, useClass: MockResultService }, - { provide: ParticipationService, useClass: MockParticipationService }, - { provide: ProgrammingExerciseParticipationService, useClass: MockProgrammingExerciseParticipationService }, - { provide: ProgrammingExerciseService, useClass: MockProgrammingExerciseService }, - { provide: WebsocketService, useClass: MockWebsocketService }, - MockProvider(ProfileService, { - getProfileInfo: () => of(mockProfileInfo), - }), - provideHttpClient(), - provideHttpClientTesting(), - ], - }) - .compileComponents() - .then(() => { - containerFixture = TestBed.createComponent(CodeEditorInstructorAndEditorContainerComponent); - comp = containerFixture.componentInstance; - containerDebugElement = containerFixture.debugElement; - - const codeEditorRepositoryService = containerDebugElement.injector.get(CodeEditorRepositoryService); - const codeEditorRepositoryFileService = containerDebugElement.injector.get(CodeEditorRepositoryFileService); - const participationWebsocketService = containerDebugElement.injector.get(ParticipationWebsocketService); - const resultService = containerDebugElement.injector.get(ResultService); - const buildLogService = containerDebugElement.injector.get(CodeEditorBuildLogService); - const programmingExerciseParticipationService = containerDebugElement.injector.get(ProgrammingExerciseParticipationService); - const programmingExerciseService = containerDebugElement.injector.get(ProgrammingExerciseService); - domainService = containerDebugElement.injector.get(DomainService); - route = containerDebugElement.injector.get(ActivatedRoute); - containerDebugElement.injector.get(Router); - checkIfRepositoryIsCleanSubject = new Subject<{ isClean: boolean }>(); - getRepositoryContentSubject = new Subject<{ [fileName: string]: FileType }>(); - subscribeForLatestResultOfParticipationSubject = new BehaviorSubject(null); - findWithParticipationsSubject = new Subject<{ body: ProgrammingExercise }>(); - - routeSubject = new Subject(); - // @ts-ignore - (route as MockActivatedRouteWithSubjects).setSubject(routeSubject); - - checkIfRepositoryIsCleanStub = jest.spyOn(codeEditorRepositoryService, 'getStatus'); - getRepositoryContentStub = jest.spyOn(codeEditorRepositoryFileService, 'getRepositoryContent'); - subscribeForLatestResultOfParticipationStub = jest.spyOn(participationWebsocketService, 'subscribeForLatestResultOfParticipation'); - getFeedbackDetailsForResultStub = jest.spyOn(resultService, 'getFeedbackDetailsForResult'); - getLatestResultWithFeedbacksStub = jest - .spyOn(programmingExerciseParticipationService, 'getLatestResultWithFeedback') - .mockReturnValue(throwError(() => new Error('no result'))); - getBuildLogsStub = jest.spyOn(buildLogService, 'getBuildLogs'); - navigateSpy = jest.spyOn(TestBed.inject(Router), 'navigate'); - - findWithParticipationsStub = jest.spyOn(programmingExerciseService, 'findWithTemplateAndSolutionParticipationAndResults'); - findWithParticipationsStub.mockReturnValue(findWithParticipationsSubject); - - subscribeForLatestResultOfParticipationStub.mockReturnValue(subscribeForLatestResultOfParticipationSubject); - getRepositoryContentStub.mockReturnValue(getRepositoryContentSubject); - checkIfRepositoryIsCleanStub.mockReturnValue(checkIfRepositoryIsCleanSubject); - }); - }); - - afterEach(() => { - jest.restoreAllMocks(); - - subscribeForLatestResultOfParticipationSubject = new BehaviorSubject(null); - subscribeForLatestResultOfParticipationStub.mockReturnValue(subscribeForLatestResultOfParticipationSubject); - - routeSubject = new Subject(); - // @ts-ignore - (route as MockActivatedRouteWithSubjects).setSubject(routeSubject); - - findWithParticipationsSubject = new Subject<{ body: ProgrammingExercise }>(); - findWithParticipationsStub.mockReturnValue(findWithParticipationsSubject); - - checkIfRepositoryIsCleanSubject = new Subject<{ isClean: boolean }>(); - checkIfRepositoryIsCleanStub.mockReturnValue(checkIfRepositoryIsCleanSubject); - - getRepositoryContentSubject = new Subject<{ [p: string]: FileType }>(); - getRepositoryContentStub.mockReturnValue(getRepositoryContentSubject); - }); - - const initContainer = (exercise: ProgrammingExercise, routeParams?: any) => { - comp.ngOnInit(); - routeSubject.next({ exerciseId: 1, ...routeParams }); - expect(comp.codeEditorContainer).toBeUndefined(); // Have to use this as it's a component - expect(findWithParticipationsStub).toHaveBeenCalledOnce(); - expect(findWithParticipationsStub).toHaveBeenCalledWith(exercise.id); - expect(comp.loadingState).toBe(comp.LOADING_STATE.INITIALIZING); - }; - - it('should load the exercise and select the template participation if no participation id is provided', () => { - jest.resetModules(); - // @ts-ignore - const exercise = { - id: 1, - problemStatement, - studentParticipations: [{ id: 2, repositoryUri: 'test' }], - templateParticipation: { id: 3, repositoryUri: 'test2', results: [{ id: 9, submission: { id: 1, buildFailed: false } }] }, - solutionParticipation: { id: 4, repositoryUri: 'test3' }, - course: { id: 1 }, - } as ProgrammingExercise; - exercise.studentParticipations = exercise.studentParticipations?.map((p) => { - p.exercise = exercise; - return p; - }); - exercise.templateParticipation = { ...exercise.templateParticipation, programmingExercise: exercise }; - exercise.solutionParticipation = { ...exercise.solutionParticipation, programmingExercise: exercise }; - - getFeedbackDetailsForResultStub.mockReturnValue(of([])); - const setDomainSpy = jest.spyOn(domainService, 'setDomain'); - // @ts-ignore - (comp.router as MockRouter).setUrl('code-editor-instructor/1'); - initContainer(exercise); - - findWithParticipationsSubject.next({ body: exercise }); - - expect(getLatestResultWithFeedbacksStub).not.toHaveBeenCalled(); - expect(setDomainSpy).toHaveBeenCalledOnce(); - expect(setDomainSpy).toHaveBeenCalledWith([DomainType.PARTICIPATION, exercise.templateParticipation]); - expect(comp.exercise).toEqual(exercise); - expect(comp.selectedRepository).toBe(RepositoryType.TEMPLATE); - expect(comp.selectedParticipation).toEqual(comp.selectedParticipation); - expect(comp.loadingState).toBe(comp.LOADING_STATE.CLEAR); - expect(comp.domainChangeSubscription).toBeDefined(); // External complex object - - containerFixture.detectChanges(); - expect(comp.codeEditorContainer.grid).toBeDefined(); // Have to use this as it's a component - - checkIfRepositoryIsCleanSubject.next({ isClean: true }); - getRepositoryContentSubject.next({ file: FileType.FILE, folder: FileType.FOLDER }); - containerFixture.detectChanges(); - - // Submission could be built - expect(getBuildLogsStub).not.toHaveBeenCalled(); - // Once called by each build-output & instructions - expect(getFeedbackDetailsForResultStub).toHaveBeenCalledTimes(2); - - expect(comp.codeEditorContainer.grid).toBeDefined(); // Have to use this as it's a component - expect(comp.codeEditorContainer.fileBrowser).toBeDefined(); // Have to use this as it's a component - expect(comp.codeEditorContainer.actions).toBeDefined(); // Have to use this as it's a component - expect(comp.editableInstructions).toBeDefined(); // Have to use this as it's a component - expect(comp.editableInstructions.participation).toEqual(exercise.templateParticipation); - expect(comp.resultComp).toBeDefined(); // Have to use this as it's a component - expect(comp.codeEditorContainer.buildOutput).toBeDefined(); // Have to use this as it's a component - - // Called once by each build-output, instructions, result and twice by instructor-exercise-status (=templateParticipation,solutionParticipation) & - expect(subscribeForLatestResultOfParticipationStub).toHaveBeenCalledTimes(5); - }); - - it('should go into error state when loading the exercise failed', () => { - const exercise = { id: 1, studentParticipations: [{ id: 2 }], templateParticipation: { id: 3 }, solutionParticipation: { id: 4 } } as ProgrammingExercise; - const setDomainSpy = jest.spyOn(domainService, 'setDomain'); - initContainer(exercise); - - findWithParticipationsSubject.error('fatal error'); - - expect(setDomainSpy).not.toHaveBeenCalled(); - expect(comp.loadingState).toBe(comp.LOADING_STATE.FETCHING_FAILED); - expect(comp.selectedRepository).toBeUndefined(); - - containerFixture.detectChanges(); - expect(comp.codeEditorContainer).toBeUndefined(); - }); - - it('should load test repository if specified in url', () => { - const exercise = { - id: 1, - problemStatement, - studentParticipations: [{ id: 2 }], - templateParticipation: { id: 3 }, - solutionParticipation: { id: 4 }, - course: { id: 1 }, - } as ProgrammingExercise; - const setDomainSpy = jest.spyOn(domainService, 'setDomain'); - // @ts-ignore - (comp.router as MockRouter).setUrl(`code-editor/TESTS`); - comp.ngOnDestroy(); - initContainer(exercise, { repositoryType: 'TESTS' }); - - findWithParticipationsSubject.next({ body: exercise }); - - expect(setDomainSpy).toHaveBeenCalledOnce(); - expect(setDomainSpy).toHaveBeenCalledWith([DomainType.TEST_REPOSITORY, exercise]); - expect(comp.selectedParticipation).toEqual(exercise.templateParticipation); - expect(comp.selectedRepository).toBe(RepositoryType.TESTS); - expect(getBuildLogsStub).not.toHaveBeenCalled(); - expect(getFeedbackDetailsForResultStub).not.toHaveBeenCalled(); - - containerFixture.detectChanges(); - - expect(comp.codeEditorContainer).toBeDefined(); // Have to use this as it's a component - expect(comp.editableInstructions).toBeDefined(); // Have to use this as it's a component - expect(comp.editableInstructions.participation).toEqual(exercise.templateParticipation); - expect(comp.resultComp).toBeUndefined(); - expect(comp.codeEditorContainer.buildOutput).toBeUndefined(); - }); - - const checkSolutionRepository = (exercise: ProgrammingExercise) => { - expect(comp.selectedRepository).toBe(RepositoryType.SOLUTION); - expect(comp.selectedParticipation).toEqual(exercise.solutionParticipation); - expect(comp.codeEditorContainer).toBeDefined(); // Have to use this as it's a component - expect(comp.editableInstructions).toBeDefined(); // Have to use this as it's a component - expect(comp.resultComp).toBeDefined(); // Have to use this as it's a component - expect(comp.codeEditorContainer.buildOutput).toBeDefined(); // Have to use this as it's a component - expect(comp.codeEditorContainer.buildOutput.participation).toEqual(exercise.solutionParticipation); - expect(comp.editableInstructions.participation).toEqual(exercise.solutionParticipation); - }; - - it('should be able to switch between the repos and update the child components accordingly', () => { - // @ts-ignore - const exercise = { - id: 1, - course: { id: 1 }, - problemStatement, - } as ProgrammingExercise; - exercise.templateParticipation = { id: 3, repositoryUri: 'test2', programmingExercise: exercise } as TemplateProgrammingExerciseParticipation; - exercise.solutionParticipation = { id: 4, repositoryUri: 'test3', programmingExercise: exercise } as SolutionProgrammingExerciseParticipation; - // @ts-ignore - exercise.studentParticipations = [{ id: 2, repositoryUri: 'test', exercise } as ProgrammingExerciseStudentParticipation]; - - const setDomainSpy = jest.spyOn(domainService, 'setDomain'); - - // Start with assignment repository - // @ts-ignore - (comp.router as MockRouter).setUrl(`code-editor/USER/2`); - - comp.ngOnInit(); - routeSubject.next({ exerciseId: 1, repositoryId: 2, repositoryType: 'USER' }); - findWithParticipationsSubject.next({ body: exercise }); - - containerFixture.detectChanges(); - - expect(comp.selectedRepository).toBe(RepositoryType.ASSIGNMENT); - expect(comp.selectedParticipation).toEqual(exercise.studentParticipations[0]); - expect(comp.codeEditorContainer).toBeDefined(); // Have to use this as it's a component - expect(comp.editableInstructions).toBeDefined(); // Have to use this as it's a component - expect(comp.resultComp).toBeDefined(); // Have to use this as it's a component - expect(comp.codeEditorContainer.buildOutput).toBeDefined(); // Have to use this as it's a component - expect(comp.codeEditorContainer.buildOutput.participation).toEqual(exercise.studentParticipations[0]); - expect(comp.editableInstructions.participation).toEqual(exercise.studentParticipations[0]); - - // New select solution repository - // @ts-ignore - (comp.router as MockRouter).setUrl('code-editor/SOLUTION/4'); - routeSubject.next({ exerciseId: 1, repositoryId: 4 }); - - containerFixture.detectChanges(); - - checkSolutionRepository(exercise); - - expect(findWithParticipationsStub).toHaveBeenCalledOnce(); - expect(findWithParticipationsStub).toHaveBeenCalledWith(exercise.id); - expect(setDomainSpy).toHaveBeenCalledTimes(2); - expect(setDomainSpy).toHaveBeenNthCalledWith(1, [DomainType.PARTICIPATION, exercise.studentParticipations[0]]); - expect(setDomainSpy).toHaveBeenNthCalledWith(2, [DomainType.PARTICIPATION, exercise.solutionParticipation]); - }); - - it('should not be able to select a repository without repositoryUri', () => { - // @ts-ignore - const exercise = { - id: 1, - course: { id: 1 }, - problemStatement, - } as ProgrammingExercise; - // @ts-ignore - exercise.studentParticipations = [{ id: 2, repositoryUri: 'test', exercise } as ProgrammingExerciseStudentParticipation]; - exercise.templateParticipation = { id: 3, programmingExercise: exercise } as TemplateProgrammingExerciseParticipation; - exercise.solutionParticipation = { id: 4, repositoryUri: 'test3', programmingExercise: exercise } as SolutionProgrammingExerciseParticipation; - - const setDomainSpy = jest.spyOn(domainService, 'setDomain'); - - // Start with assignment repository - // @ts-ignore - (comp.router as MockRouter).setUrl('code-editor-instructor/1/3'); - comp.ngOnInit(); - routeSubject.next({ exerciseId: 1, participationId: 3 }); - findWithParticipationsSubject.next({ body: exercise }); - - containerFixture.detectChanges(); - - expect(setDomainSpy).toHaveBeenCalledOnce(); - expect(setDomainSpy).toHaveBeenCalledWith([DomainType.PARTICIPATION, exercise.solutionParticipation]); - checkSolutionRepository(exercise); - }); - - describe('Repository Navigation', () => { - const exercise = { - id: 1, - problemStatement, - studentParticipations: [{ id: 2 }], - templateParticipation: { id: 3 }, - solutionParticipation: { id: 4 }, - course: { id: 1 }, - } as ProgrammingExercise; - - beforeEach(() => { - comp.exercise = exercise; - }); - - it('should navigate to template participation repository from auxiliary repository', () => { - comp.selectedRepository = RepositoryType.AUXILIARY; - comp.selectTemplateParticipation(); - expect(navigateSpy).toHaveBeenCalledWith(['../..', RepositoryType.TEMPLATE, exercise.templateParticipation!.id], expect.any(Object)); - }); - - it('should navigate to template participation repository from test repository', () => { - comp.selectedRepository = RepositoryType.TESTS; - comp.selectTemplateParticipation(); - expect(navigateSpy).toHaveBeenCalledWith(['../..', RepositoryType.TEMPLATE, exercise.templateParticipation!.id], expect.any(Object)); - }); - - it('should navigate to solution participation repository from auxiliary repository', () => { - comp.selectedRepository = RepositoryType.AUXILIARY; - comp.selectSolutionParticipation(); - expect(navigateSpy).toHaveBeenCalledWith(['../..', RepositoryType.SOLUTION, exercise.solutionParticipation!.id], expect.any(Object)); - }); - - it('should navigate to solution participation repository from test repository', () => { - comp.selectedRepository = RepositoryType.TESTS; - comp.selectSolutionParticipation(); - expect(navigateSpy).toHaveBeenCalledWith(['../..', RepositoryType.SOLUTION, exercise.solutionParticipation!.id], expect.any(Object)); - }); - - it('should navigate to assignment participation repository from auxiliary repository', () => { - comp.selectedRepository = RepositoryType.AUXILIARY; - comp.selectAssignmentParticipation(); - expect(navigateSpy).toHaveBeenCalledWith(['../..', RepositoryType.USER, exercise.studentParticipations![0].id], expect.any(Object)); - }); - - it('should navigate to assignment participation repository from test repository', () => { - comp.selectedRepository = RepositoryType.TESTS; - comp.selectAssignmentParticipation(); - expect(navigateSpy).toHaveBeenCalledWith(['../..', RepositoryType.USER, exercise.studentParticipations![0].id], expect.any(Object)); - }); - - it('should navigate to test repository from auxiliary repository', () => { - comp.selectedRepository = RepositoryType.AUXILIARY; - comp.selectTestRepository(); - expect(navigateSpy).toHaveBeenCalledWith(['../..', RepositoryType.TESTS, 'test'], expect.any(Object)); - }); - - it('should navigate to test repository from test repository', () => { - comp.selectedRepository = RepositoryType.TESTS; - comp.selectTestRepository(); - expect(navigateSpy).toHaveBeenCalledWith(['../..', RepositoryType.TESTS, 'test'], expect.any(Object)); - }); - - it('should navigate to auxiliary repository with provided repositoryId', () => { - const repositoryId = 4; - comp.selectedRepository = RepositoryType.AUXILIARY; - comp.selectAuxiliaryRepository(repositoryId); - expect(navigateSpy).toHaveBeenCalledWith(['../..', RepositoryType.AUXILIARY, repositoryId], expect.any(Object)); - }); - - it('should navigate to auxiliary repository from test repository', () => { - const repositoryId = 4; - comp.selectedRepository = RepositoryType.TESTS; - comp.selectAuxiliaryRepository(repositoryId); - expect(navigateSpy).toHaveBeenCalledWith(['../..', RepositoryType.AUXILIARY, repositoryId], expect.any(Object)); - }); - }); -}); diff --git a/src/test/javascript/spec/integration/code-editor/code-editor-student.integration.spec.ts b/src/test/javascript/spec/integration/code-editor/code-editor-student.integration.spec.ts deleted file mode 100644 index a9f77d4faad4..000000000000 --- a/src/test/javascript/spec/integration/code-editor/code-editor-student.integration.spec.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; -import dayjs from 'dayjs/esm'; -import { JhiLanguageHelper } from 'app/core/language/shared/language.helper'; -import { AccountService } from 'app/core/auth/account.service'; -import { DebugElement } from '@angular/core'; -import { ActivatedRoute, Params } from '@angular/router'; -import { BehaviorSubject, Subject } from 'rxjs'; -import { ParticipationWebsocketService } from 'app/core/course/shared/services/participation-websocket.service'; -import { ProgrammingExerciseParticipationService } from 'app/programming/manage/services/programming-exercise-participation.service'; -import { CommitState } from 'app/programming/shared/code-editor/model/code-editor.model'; -import { MockAccountService } from '../../helpers/mocks/service/mock-account.service'; -import { MockProgrammingExerciseParticipationService } from '../../helpers/mocks/service/mock-programming-exercise-participation.service'; -import { ProgrammingSubmissionService } from 'app/programming/shared/services/programming-submission.service'; -import { MockProgrammingSubmissionService } from '../../helpers/mocks/service/mock-programming-submission.service'; -import { getElement } from '../../helpers/utils/general.utils'; -import { WebsocketService } from 'app/shared/service/websocket.service'; -import { MockWebsocketService } from '../../helpers/mocks/service/mock-websocket.service'; -import { Participation } from 'app/exercise/shared/entities/participation/participation.model'; -import { ResultService } from 'app/exercise/result/result.service'; -import { Result } from 'app/exercise/shared/entities/result/result.model'; -import { - CodeEditorBuildLogService, - CodeEditorRepositoryFileService, - CodeEditorRepositoryService, -} from 'app/programming/shared/code-editor/services/code-editor-repository.service'; -import { Feedback } from 'app/assessment/shared/entities/feedback.model'; -import { CodeEditorStudentContainerComponent } from 'app/programming/overview/code-editor-student-container/code-editor-student-container.component'; -import { ProgrammingExercise } from 'app/programming/shared/entities/programming-exercise.model'; -import { MockActivatedRouteWithSubjects } from '../../helpers/mocks/activated-route/mock-activated-route-with-subjects'; -import { MockParticipationWebsocketService } from '../../helpers/mocks/service/mock-participation-websocket.service'; -import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; -import { MockResultService } from '../../helpers/mocks/service/mock-result.service'; -import { MockCodeEditorRepositoryService } from '../../helpers/mocks/service/mock-code-editor-repository.service'; -import { MockCodeEditorRepositoryFileService } from '../../helpers/mocks/service/mock-code-editor-repository-file.service'; -import { MockCodeEditorBuildLogService } from '../../helpers/mocks/service/mock-code-editor-build-log.service'; -import { MockComponent, MockModule, MockPipe } from 'ng-mocks'; -import { CodeEditorContainerComponent } from 'app/programming/manage/code-editor/container/code-editor-container.component'; -import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; -import { IncludedInScoreBadgeComponent } from 'app/exercise/exercise-headers/included-in-score-badge/included-in-score-badge.component'; -import { CodeEditorRepositoryIsLockedComponent } from 'app/programming/shared/code-editor/layout/code-editor-repository-is-locked.component'; -import { UpdatingResultComponent } from 'app/exercise/result/updating-result/updating-result.component'; -import { ProgrammingExerciseStudentTriggerBuildButtonComponent } from 'app/programming/shared/actions/trigger-build-button/student/programming-exercise-student-trigger-build-button.component'; -import { ProgrammingExerciseInstructionComponent } from 'app/programming/shared/instructions-render/programming-exercise-instruction.component'; -import { AdditionalFeedbackComponent } from 'app/exercise/additional-feedback/additional-feedback.component'; -import { CodeEditorGridComponent } from 'app/programming/shared/code-editor/layout/code-editor-grid/code-editor-grid.component'; -import { CodeEditorInstructionsComponent } from 'app/programming/shared/code-editor/instructions/code-editor-instructions.component'; -import { KeysPipe } from 'app/shared/pipes/keys.pipe'; -import { CodeEditorActionsComponent } from 'app/programming/shared/code-editor/actions/code-editor-actions.component'; -import { CodeEditorFileBrowserComponent } from 'app/programming/manage/code-editor/file-browser/code-editor-file-browser.component'; -import { CodeEditorBuildOutputComponent } from 'app/programming/manage/code-editor/build-output/code-editor-build-output.component'; -import { CodeEditorFileBrowserCreateNodeComponent } from 'app/programming/manage/code-editor/file-browser/create-node/code-editor-file-browser-create-node.component'; -import { CodeEditorFileBrowserFolderComponent } from 'app/programming/manage/code-editor/file-browser/folder/code-editor-file-browser-folder.component'; -import { CodeEditorFileBrowserFileComponent } from 'app/programming/manage/code-editor/file-browser/file/code-editor-file-browser-file.component'; -import { CodeEditorStatusComponent } from 'app/programming/shared/code-editor/status/code-editor-status.component'; -import { TreeViewComponent } from 'app/programming/shared/code-editor/treeview/components/tree-view/tree-view.component'; -import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; -import { CodeEditorMonacoComponent } from 'app/programming/shared/code-editor/monaco/code-editor-monaco.component'; -import { mockCodeEditorMonacoViewChildren } from '../../helpers/mocks/mock-instance.helper'; -import { provideHttpClientTesting } from '@angular/common/http/testing'; -import { provideHttpClient } from '@angular/common/http'; -import { MockTranslateService } from '../../helpers/mocks/service/mock-translate.service'; -import { TranslateService } from '@ngx-translate/core'; - -describe('CodeEditorStudentIntegration', () => { - let container: CodeEditorStudentContainerComponent; - let containerFixture: ComponentFixture; - let containerDebugElement: DebugElement; - let codeEditorRepositoryService: CodeEditorRepositoryService; - let participationWebsocketService: ParticipationWebsocketService; - let resultService: ResultService; - let programmingExerciseParticipationService: ProgrammingExerciseParticipationService; - let route: ActivatedRoute; - - let checkIfRepositoryIsCleanStub: jest.SpyInstance; - let subscribeForLatestResultOfParticipationStub: jest.SpyInstance; - let getFeedbackDetailsForResultStub: jest.SpyInstance; - let getStudentParticipationWithLatestResultStub: jest.SpyInstance; - - let subscribeForLatestResultOfParticipationSubject: BehaviorSubject; - let routeSubject: Subject; - - const result: Result = { id: 3, successful: false, completionDate: dayjs().subtract(2, 'days') }; - - // Workaround for an error with MockComponent(). You can remove this once https://github.com/help-me-mom/ng-mocks/issues/8634 is resolved. - mockCodeEditorMonacoViewChildren(); - - beforeEach(() => { - return TestBed.configureTestingModule({ - imports: [MockModule(NgbTooltipModule)], - declarations: [ - CodeEditorStudentContainerComponent, - CodeEditorContainerComponent, - MockComponent(CodeEditorFileBrowserComponent), - MockComponent(CodeEditorInstructionsComponent), - CodeEditorRepositoryIsLockedComponent, - MockPipe(KeysPipe), - MockComponent(IncludedInScoreBadgeComponent), - MockComponent(UpdatingResultComponent), - MockComponent(ProgrammingExerciseStudentTriggerBuildButtonComponent), - MockComponent(ProgrammingExerciseInstructionComponent), - MockComponent(AdditionalFeedbackComponent), - MockPipe(ArtemisTranslatePipe), - MockComponent(CodeEditorGridComponent), - MockComponent(CodeEditorActionsComponent), - MockComponent(CodeEditorBuildOutputComponent), - MockComponent(CodeEditorMonacoComponent), - MockComponent(CodeEditorFileBrowserCreateNodeComponent), - MockComponent(CodeEditorFileBrowserFolderComponent), - MockComponent(CodeEditorFileBrowserFileComponent), - MockComponent(CodeEditorStatusComponent), - TreeViewComponent, - ], - providers: [ - JhiLanguageHelper, - { provide: AccountService, useClass: MockAccountService }, - { provide: ActivatedRoute, useClass: MockActivatedRouteWithSubjects }, - { provide: WebsocketService, useClass: MockWebsocketService }, - { provide: ParticipationWebsocketService, useClass: MockParticipationWebsocketService }, - { provide: ProgrammingExerciseParticipationService, useClass: MockProgrammingExerciseParticipationService }, - { provide: SessionStorageService, useClass: MockSyncStorage }, - { provide: ResultService, useClass: MockResultService }, - { provide: LocalStorageService, useClass: MockSyncStorage }, - { provide: CodeEditorRepositoryService, useClass: MockCodeEditorRepositoryService }, - { provide: CodeEditorRepositoryFileService, useClass: MockCodeEditorRepositoryFileService }, - { provide: CodeEditorBuildLogService, useClass: MockCodeEditorBuildLogService }, - { provide: ResultService, useClass: MockResultService }, - { provide: ProgrammingSubmissionService, useClass: MockProgrammingSubmissionService }, - { provide: TranslateService, useClass: MockTranslateService }, - provideHttpClient(), - provideHttpClientTesting(), - ], - }) - .compileComponents() - .then(() => { - containerFixture = TestBed.createComponent(CodeEditorStudentContainerComponent); - container = containerFixture.componentInstance; - containerDebugElement = containerFixture.debugElement; - - codeEditorRepositoryService = TestBed.inject(CodeEditorRepositoryService); - participationWebsocketService = TestBed.inject(ParticipationWebsocketService); - resultService = TestBed.inject(ResultService); - programmingExerciseParticipationService = TestBed.inject(ProgrammingExerciseParticipationService); - route = TestBed.inject(ActivatedRoute); - - subscribeForLatestResultOfParticipationSubject = new BehaviorSubject(undefined); - - routeSubject = new Subject(); - // @ts-ignore - (route as MockActivatedRouteWithSubjects).setSubject(routeSubject); - - checkIfRepositoryIsCleanStub = jest.spyOn(codeEditorRepositoryService, 'getStatus'); - subscribeForLatestResultOfParticipationStub = jest - .spyOn(participationWebsocketService, 'subscribeForLatestResultOfParticipation') - .mockReturnValue(subscribeForLatestResultOfParticipationSubject); - getFeedbackDetailsForResultStub = jest.spyOn(resultService, 'getFeedbackDetailsForResult'); - getStudentParticipationWithLatestResultStub = jest.spyOn(programmingExerciseParticipationService, 'getStudentParticipationWithLatestResult'); - }); - }); - - afterEach(() => { - jest.restoreAllMocks(); - - subscribeForLatestResultOfParticipationSubject = new BehaviorSubject(undefined); - subscribeForLatestResultOfParticipationStub.mockReturnValue(subscribeForLatestResultOfParticipationSubject); - - routeSubject = new Subject(); - // @ts-ignore - (route as MockActivatedRouteWithSubjects).setSubject(routeSubject); - }); - - it('should initialize correctly on route change if participation can be retrieved', () => { - container.ngOnInit(); - const feedbacks = [{ id: 2 }] as Feedback[]; - result.feedbacks = feedbacks; - const participation = { id: 1, results: [result], exercise: { id: 99 } } as Participation; - const findWithLatestResultSubject = new Subject(); - getStudentParticipationWithLatestResultStub.mockReturnValue(findWithLatestResultSubject); - - routeSubject.next({ participationId: 1 }); - - expect(container.loadingParticipation).toBeTrue(); - - findWithLatestResultSubject.next(participation); - - expect(getStudentParticipationWithLatestResultStub).toHaveBeenNthCalledWith(1, participation.id); - expect(container.loadingParticipation).toBeFalse(); - expect(container.participationCouldNotBeFetched).toBeFalse(); - expect(container.participation).toEqual({ ...participation, results: [{ ...result, feedbacks }] }); - }); - - // TODO re-enable after remove-gitalb issues are resolved - it.skip('should show the repository locked badge and disable the editor actions if the participation is locked', () => { - container.ngOnInit(); - const participation = { - id: 1, - results: [result], - exercise: { id: 99, dueDate: dayjs().subtract(2, 'hours') } as ProgrammingExercise, - locked: true, - } as any; - const feedbacks = [{ id: 2 }] as Feedback[]; - const findWithLatestResultSubject = new Subject(); - const getFeedbackDetailsForResultSubject = new Subject<{ body: Feedback[] }>(); - const isCleanSubject = new Subject(); - getStudentParticipationWithLatestResultStub.mockReturnValue(findWithLatestResultSubject); - getFeedbackDetailsForResultStub.mockReturnValue(getFeedbackDetailsForResultSubject); - checkIfRepositoryIsCleanStub.mockReturnValue(isCleanSubject); - - routeSubject.next({ participationId: 1 }); - findWithLatestResultSubject.next(participation); - getFeedbackDetailsForResultSubject.next({ body: feedbacks }); - - containerFixture.detectChanges(); - isCleanSubject.next({ repositoryStatus: CommitState.CLEAN }); - - // Repository should be locked, the student can't write into it anymore. - expect(container.repositoryIsLocked).toBeTrue(); - expect(getElement(containerDebugElement, '.locked-container').innerHTML).toContain('fa-icon'); - expect(container.codeEditorContainer.fileBrowser.disableActions).toBeTrue(); - expect(container.codeEditorContainer.actions.disableActions).toBeTrue(); - }); - - it('should abort initialization and show error state if participation cannot be retrieved', () => { - container.ngOnInit(); - const findWithLatestResultSubject = new Subject<{ body: Participation }>(); - getStudentParticipationWithLatestResultStub.mockReturnValue(findWithLatestResultSubject); - - routeSubject.next({ participationId: 1 }); - - expect(container.loadingParticipation).toBeTrue(); - - findWithLatestResultSubject.error('fatal error'); - - expect(container.loadingParticipation).toBeFalse(); - expect(container.participationCouldNotBeFetched).toBeTrue(); - expect(getFeedbackDetailsForResultStub).not.toHaveBeenCalled(); - expect(container.participation).toBeUndefined(); - }); -}); diff --git a/src/test/javascript/spec/integration/guided-tour/guided-tour.integration.spec.ts b/src/test/javascript/spec/integration/guided-tour/guided-tour.integration.spec.ts deleted file mode 100644 index fd3a6aa0d3cc..000000000000 --- a/src/test/javascript/spec/integration/guided-tour/guided-tour.integration.spec.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; -import { of } from 'rxjs'; -import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; -import { GuidedTourService } from 'app/core/guided-tour/guided-tour.service'; -import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; -import { User } from 'app/core/user/user.model'; -import { CourseCardComponent } from 'app/core/course/overview/course-card/course-card.component'; -import { Course } from 'app/core/course/shared/entities/course.model'; -import { ARTEMIS_DEFAULT_COLOR } from 'app/app.constants'; -import { ExerciseService } from 'app/exercise/services/exercise.service'; -import { MockDirective } from 'ng-mocks'; -import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; -import { NgbCollapse } from '@ng-bootstrap/ng-bootstrap'; -import { provideHttpClientTesting } from '@angular/common/http/testing'; -import { provideHttpClient } from '@angular/common/http'; -import { MockTranslateService } from '../../helpers/mocks/service/mock-translate.service'; -import { TranslateService } from '@ngx-translate/core'; -import { MockActivatedRoute } from '../../helpers/mocks/activated-route/mock-activated-route'; -import { ActivatedRoute } from '@angular/router'; -import { AccountService } from 'app/core/auth/account.service'; -import { MockAccountService } from '../../helpers/mocks/service/mock-account.service'; -import { GuidedTourComponent } from 'app/core/guided-tour/guided-tour.component'; -import { courseOverviewTour } from 'app/core/guided-tour/tours/course-overview-tour'; -import { FeatureToggle, FeatureToggleService } from 'app/shared/feature-toggle/feature-toggle.service'; -import { NavbarComponent } from 'app/core/navbar/navbar.component'; -import { FooterComponent } from 'app/core/layouts/footer/footer.component'; - -class MockFeatureToggleService implements Partial { - private featureToggles = new Map(); - - constructor() { - Object.values(FeatureToggle).forEach((toggle) => { - this.featureToggles.set(toggle, false); - }); - } - - getFeatureToggleActive(feature: FeatureToggle) { - return of(this.featureToggles.get(feature) || false); - } - - setMockFeatureToggle(feature: FeatureToggle, active: boolean) { - this.featureToggles.set(feature, active); - } -} - -describe('Guided tour integration', () => { - const user = { id: 1 } as User; - const course = { id: 1, color: ARTEMIS_DEFAULT_COLOR } as Course; - let guidedTourComponent: GuidedTourComponent; - let guidedTourComponentFixture: ComponentFixture; - let navBarComponent: NavbarComponent; - let navBarComponentFixture: ComponentFixture; - let courseCardComponent: CourseCardComponent; - let courseCardComponentFixture: ComponentFixture; - let footerComponentFixture: ComponentFixture; - let guidedTourService: GuidedTourService; - let exerciseService: ExerciseService; - let featureToggleService: MockFeatureToggleService; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [MockDirective(NgbCollapse)], - declarations: [GuidedTourComponent], - providers: [ - { provide: LocalStorageService, useClass: MockSyncStorage }, - { provide: SessionStorageService, useClass: MockSyncStorage }, - { provide: ArtemisTranslatePipe, useClass: ArtemisTranslatePipe }, - { provide: TranslateService, useClass: MockTranslateService }, - { provide: ActivatedRoute, useValue: new MockActivatedRoute() }, - { provide: AccountService, useClass: MockAccountService }, - { provide: FeatureToggleService, useClass: MockFeatureToggleService }, - provideHttpClient(), - provideHttpClientTesting(), - ], - }).compileComponents(); - - featureToggleService = TestBed.inject(FeatureToggleService) as unknown as MockFeatureToggleService; - - featureToggleService.setMockFeatureToggle(FeatureToggle.CourseSpecificNotifications, false); - - guidedTourComponentFixture = TestBed.createComponent(GuidedTourComponent); - courseCardComponentFixture = TestBed.createComponent(CourseCardComponent); - navBarComponentFixture = TestBed.createComponent(NavbarComponent); - footerComponentFixture = TestBed.createComponent(FooterComponent); - - guidedTourComponent = guidedTourComponentFixture.componentInstance; - navBarComponent = navBarComponentFixture.componentInstance; - courseCardComponent = courseCardComponentFixture.componentInstance; - - guidedTourService = TestBed.inject(GuidedTourService); - exerciseService = TestBed.inject(ExerciseService); - - jest.spyOn(navBarComponentFixture.componentInstance, 'ngOnInit').mockImplementation(() => { - navBarComponent.currAccount = user; - }); - - jest.spyOn(guidedTourComponent, 'subscribeToDotChanges').mockReturnValue(of()); - jest.spyOn(exerciseService, 'getNextExercisesForDays').mockReturnValue([]); - jest.spyOn(guidedTourService, 'init').mockImplementation(); - jest.spyOn(guidedTourService, 'updateGuidedTourSettings').mockReturnValue(of()); - jest.spyOn(guidedTourService, 'checkTourState').mockReturnValue(true); - jest.spyOn(guidedTourService, 'checkSelectorValidity').mockReturnValue(true); - jest.spyOn(guidedTourService, 'enableTour').mockImplementation(() => { - guidedTourService['availableTourForComponent'] = courseOverviewTour; - guidedTourService.currentTour = courseOverviewTour; - }); - }); - - function startGuidedTour() { - guidedTourComponentFixture.componentInstance.ngAfterViewInit(); - - // Start course overview tour - guidedTourService['enableTour'](courseOverviewTour, true); - guidedTourService['startTour'](); - - guidedTourComponentFixture.detectChanges(); - navBarComponentFixture.detectChanges(); - expect(guidedTourComponentFixture.debugElement.query(By.css('.tour-step'))).not.toBeNull(); - } - - describe('Course Overview Tour', () => { - beforeEach(() => { - startGuidedTour(); - - courseCardComponent.course = course; - courseCardComponent.hasGuidedTour = true; - }); - - it('should start the course overview guided tour', () => { - window.scrollTo = () => {}; - - const guidedTourSteps = guidedTourService['getFilteredTourSteps']().length; - guidedTourComponentFixture.autoDetectChanges(true); - courseCardComponentFixture.autoDetectChanges(true); - navBarComponentFixture.autoDetectChanges(true); - - expect(guidedTourService.isOnFirstStep).toBeTrue(); - expect(guidedTourSteps).toBe(9); - - // Click through tour steps in NavComponent - for (let i = 1; i < 6; i++) { - guidedTourService.nextStep(); - expect(guidedTourService.isOnFirstStep).toBeFalse(); - guidedTourComponent.currentTourStep = guidedTourService['currentStep']; - - if (guidedTourComponent.currentTourStep.highlightSelector) { - const selectedElement = navBarComponentFixture.debugElement.query(By.css(guidedTourComponent.currentTourStep.highlightSelector)); - expect(selectedElement).not.toBeNull(); - } - } - - // Click through tour steps in CourseCardComponent - for (let i = 6; i < 8; i++) { - guidedTourService.nextStep(); - guidedTourComponent.currentTourStep = guidedTourService['currentStep']; - - if (guidedTourComponent.currentTourStep.highlightSelector) { - const selectedElement = courseCardComponentFixture.debugElement.query(By.css(guidedTourComponent.currentTourStep.highlightSelector)); - expect(selectedElement).not.toBeNull(); - } - } - - // Click through tour steps in FooterComponent - for (let i = 8; i < guidedTourSteps; i++) { - guidedTourService.nextStep(); - guidedTourComponent.currentTourStep = guidedTourService['currentStep']; - - if (guidedTourComponent.currentTourStep.highlightSelector) { - const selectedElement = footerComponentFixture.debugElement.query(By.css(guidedTourComponent.currentTourStep.highlightSelector)); - expect(selectedElement).not.toBeNull(); - } - } - - expect(guidedTourService.isOnLastStep).toBeTrue(); - }); - }); -}); diff --git a/src/test/javascript/spec/integration/monaco-editor/monaco-editor-action-quiz.integration.spec.ts b/src/test/javascript/spec/integration/monaco-editor/monaco-editor-action-quiz.integration.spec.ts deleted file mode 100644 index f9d09ebf822b..000000000000 --- a/src/test/javascript/spec/integration/monaco-editor/monaco-editor-action-quiz.integration.spec.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; -import { MockResizeObserver } from '../../helpers/mocks/service/mock-resize-observer'; -import { InsertShortAnswerOptionAction } from 'app/shared/monaco-editor/model/actions/quiz/insert-short-answer-option.action'; -import { InsertShortAnswerSpotAction } from 'app/shared/monaco-editor/model/actions/quiz/insert-short-answer-spot.action'; -import { WrongMultipleChoiceAnswerAction } from 'app/shared/monaco-editor/model/actions/quiz/wrong-multiple-choice-answer.action'; -import { CorrectMultipleChoiceAnswerAction } from 'app/shared/monaco-editor/model/actions/quiz/correct-multiple-choice-answer.action'; -import { QuizExplanationAction } from 'app/shared/monaco-editor/model/actions/quiz/quiz-explanation.action'; -import { QuizHintAction } from 'app/shared/monaco-editor/model/actions/quiz/quiz-hint.action'; -import { MockTranslateService } from '../../helpers/mocks/service/mock-translate.service'; -import { TranslateService } from '@ngx-translate/core'; -import { ThemeService } from 'app/core/theme/shared/theme.service'; -import { MockThemeService } from '../../helpers/mocks/service/mock-theme.service'; - -describe('MonacoEditorActionQuizIntegration', () => { - let fixture: ComponentFixture; - let comp: MonacoEditorComponent; - - // Actions - let insertShortAnswerOptionAction: InsertShortAnswerOptionAction; - let insertShortAnswerSpotAction: InsertShortAnswerSpotAction; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [MonacoEditorComponent], - providers: [ - { provide: TranslateService, useClass: MockTranslateService }, - { provide: ThemeService, useClass: MockThemeService }, - ], - }) - .compileComponents() - .then(() => { - fixture = TestBed.createComponent(MonacoEditorComponent); - comp = fixture.componentInstance; - global.ResizeObserver = jest.fn().mockImplementation((callback: ResizeObserverCallback) => { - return new MockResizeObserver(callback); - }); - insertShortAnswerOptionAction = new InsertShortAnswerOptionAction(); - insertShortAnswerSpotAction = new InsertShortAnswerSpotAction(insertShortAnswerOptionAction); - fixture.detectChanges(); - comp.registerAction(insertShortAnswerOptionAction); - comp.registerAction(insertShortAnswerSpotAction); - }); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - describe('MonacoInsertShortAnswerOption', () => { - const getLastLine = () => comp.getText().split('\n').last(); - - it('should insert text with the default answer option', () => { - insertShortAnswerOptionAction.executeInCurrentEditor(); - // Text must match - expect(getLastLine()).toBe(`[-option #] ${InsertShortAnswerOptionAction.DEFAULT_TEXT}`); - // Also test if the selection works. Type the option text. - comp.triggerKeySequence('This is an actual option!'); - expect(getLastLine()).toBe('[-option #] This is an actual option!'); - }); - - it('should insert the default text for blank option texts', () => { - insertShortAnswerOptionAction.executeInCurrentEditor({ optionText: '' }); - expect(getLastLine()).toBe(`[-option #] ${InsertShortAnswerOptionAction.DEFAULT_TEXT}`); - }); - - it('should insert text with the specified spot number', () => { - insertShortAnswerOptionAction.executeInCurrentEditor({ spotNumber: 5 }); - // Text must match - expect(getLastLine()).toBe(`[-option 5] ${InsertShortAnswerOptionAction.DEFAULT_TEXT}`); - // Also test if the selection works. Type the option text. - comp.triggerKeySequence('This is an actual option!'); - expect(getLastLine()).toBe('[-option 5] This is an actual option!'); - }); - - it('should insert text with the specified option text and spot number', () => { - insertShortAnswerOptionAction.executeInCurrentEditor({ optionText: 'This is a custom option!', spotNumber: 5 }); - expect(getLastLine()).toBe('[-option 5] This is a custom option!'); - }); - - it('should insert text with the specified option text', () => { - insertShortAnswerOptionAction.executeInCurrentEditor({ optionText: 'This is a custom option!' }); - expect(getLastLine()).toBe('[-option #] This is a custom option!'); - }); - - it('should insert text after the last option', () => { - const text = '[-option 1] Option 1\n[-option 2] Option 2\n[-option 3] Option 3'; - const expectedText = text + `\n[-option #] ${InsertShortAnswerOptionAction.DEFAULT_TEXT}`; - comp.setText('[-option 1] Option 1\n[-option 2] Option 2\n[-option 3] Option 3'); - insertShortAnswerOptionAction.executeInCurrentEditor(); - expect(comp.getText()).toBe(expectedText); - }); - - it('should insert text with more space if the last line is not an option', () => { - const text = 'Some question text\nof a question\nwith lines'; - const expectedText = text + `\n\n\n[-option #] ${InsertShortAnswerOptionAction.DEFAULT_TEXT}`; - comp.setText(text); - insertShortAnswerOptionAction.executeInCurrentEditor(); - expect(comp.getText()).toBe(expectedText); - }); - }); - - describe('EditorInsertShortAnswerSpotAction', () => { - let insertAnswerOptionActionExecuteStub: jest.SpyInstance; - - beforeEach(() => { - insertAnswerOptionActionExecuteStub = jest.spyOn(insertShortAnswerOptionAction, 'executeInCurrentEditor').mockImplementation(); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('should forward the spot number & selection to the insertShortAnswerOptionAction', () => { - comp.setText('Some text of a question'); - comp.setSelection({ startLineNumber: 1, startColumn: 6, endLineNumber: 1, endColumn: 10 }); - insertShortAnswerSpotAction.spotNumber = 5; - insertShortAnswerSpotAction.executeInCurrentEditor(); - expect(insertAnswerOptionActionExecuteStub).toHaveBeenCalledWith({ spotNumber: 5, optionText: 'text' }); - }); - - it('should insert a spot at the current position (no selection)', () => { - const text = 'I am about to put a spot here: '; - // Type the text so the cursor moves along - comp.triggerKeySequence(text); - insertShortAnswerSpotAction.executeInCurrentEditor(); - expect(comp.getText()).toBe(text + '[-spot 1]'); - }); - - it('should insert a spot at the current position (with selection)', () => { - comp.setText('I am about to put a spot here.'); - comp.setSelection({ startLineNumber: 1, startColumn: 21, endLineNumber: 1, endColumn: 25 }); - insertShortAnswerSpotAction.executeInCurrentEditor(); - expect(comp.getText()).toBe('I am about to put a [-spot 1] here.'); - }); - - it('should insert a spot and attach an option', () => { - insertAnswerOptionActionExecuteStub.mockRestore(); - const questionText = 'This is a question.\nIt can have a spot where students can put an answer, e.g.: '; - // Type the text so the cursor moves along - comp.triggerKeySequence(questionText); - insertShortAnswerSpotAction.executeInCurrentEditor(); - expect(comp.getText()).toBe(questionText + '[-spot 1]' + `\n\n\n[-option 1] ${InsertShortAnswerOptionAction.DEFAULT_TEXT}`); - }); - }); - - describe('Multiple Choice answer options', () => { - it('should insert a wrong MC option', () => { - comp.triggerKeySequence('This is a question that needs some options.'); - const action = new WrongMultipleChoiceAnswerAction(); - comp.registerAction(action); - action.executeInCurrentEditor(); - expect(comp.getText()).toBe('This is a question that needs some options.\n[wrong] Enter a wrong answer option here'); - }); - - it('should insert a correct MC option', () => { - comp.triggerKeySequence('This is a question that needs some options.'); - const action = new CorrectMultipleChoiceAnswerAction(); - comp.registerAction(action); - action.executeInCurrentEditor(); - expect(comp.getText()).toBe('This is a question that needs some options.\n[correct] Enter a correct answer option here'); - }); - - it('should add an explanation to an answer option', () => { - comp.triggerKeySequence('This is a question that has an option.\n[correct] Option 1'); - const action = new QuizExplanationAction(); - comp.registerAction(action); - action.executeInCurrentEditor(); - expect(comp.getText()).toBe( - 'This is a question that has an option.\n[correct] Option 1\n\t[exp] Add an explanation here (only visible in feedback after quiz has ended)', - ); - }); - - it('should add a hint to an answer option', () => { - comp.triggerKeySequence('This is a question that has an option.\n[correct] Option 1'); - const action = new QuizHintAction(); - comp.registerAction(action); - action.executeInCurrentEditor(); - expect(comp.getText()).toBe('This is a question that has an option.\n[correct] Option 1\n\t[hint] Add a hint here (visible during the quiz via ?-Button)'); - }); - }); -}); diff --git a/src/test/javascript/spec/integration/monaco-editor/monaco-editor-action.integration.spec.ts b/src/test/javascript/spec/integration/monaco-editor/monaco-editor-action.integration.spec.ts deleted file mode 100644 index a17939b01be5..000000000000 --- a/src/test/javascript/spec/integration/monaco-editor/monaco-editor-action.integration.spec.ts +++ /dev/null @@ -1,386 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; -import { MockResizeObserver } from '../../helpers/mocks/service/mock-resize-observer'; -import { BoldAction } from 'app/shared/monaco-editor/model/actions/bold.action'; -import { TextEditorAction } from 'app/shared/monaco-editor/model/actions/text-editor-action.model'; -import { ItalicAction } from 'app/shared/monaco-editor/model/actions/italic.action'; -import { CodeAction } from 'app/shared/monaco-editor/model/actions/code.action'; -import { ColorAction } from 'app/shared/monaco-editor/model/actions/color.action'; -import { UnderlineAction } from 'app/shared/monaco-editor/model/actions/underline.action'; -import { CodeBlockAction } from 'app/shared/monaco-editor/model/actions/code-block.action'; -import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.action'; -import { QuoteAction } from 'app/shared/monaco-editor/model/actions/quote.action'; -import { FullscreenAction } from 'app/shared/monaco-editor/model/actions/fullscreen.action'; -import * as FullscreenUtil from 'app/shared/util/fullscreen.util'; -import { TaskAction } from 'app/shared/monaco-editor/model/actions/task.action'; -import { TestCaseAction } from 'app/shared/monaco-editor/model/actions/test-case.action'; -import { HeadingAction } from 'app/shared/monaco-editor/model/actions/heading.action'; -import { UrlAction } from 'app/shared/monaco-editor/model/actions/url.action'; -import { AttachmentAction } from 'app/shared/monaco-editor/model/actions/attachment.action'; -import { OrderedListAction } from 'app/shared/monaco-editor/model/actions/ordered-list.action'; -import { UnorderedListAction } from 'app/shared/monaco-editor/model/actions/unordered-list.action'; -import * as monaco from 'monaco-editor'; -import { MockClipboardItem } from '../../helpers/mocks/service/mock-clipboard-item'; -import { MockTranslateService } from '../../helpers/mocks/service/mock-translate.service'; -import { TranslateService } from '@ngx-translate/core'; -import { MockThemeService } from '../../helpers/mocks/service/mock-theme.service'; -import { ThemeService } from 'app/core/theme/shared/theme.service'; - -describe('MonacoEditorActionIntegration', () => { - let fixture: ComponentFixture; - let comp: MonacoEditorComponent; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [MonacoEditorComponent], - providers: [ - { provide: TranslateService, useClass: MockTranslateService }, - { provide: ThemeService, useClass: MockThemeService }, - ], - }).compileComponents(); - - fixture = TestBed.createComponent(MonacoEditorComponent); - comp = fixture.componentInstance; - global.ResizeObserver = jest.fn().mockImplementation((callback: ResizeObserverCallback) => { - return new MockResizeObserver(callback); - }); - - Object.assign(navigator, { - clipboard: { - read: jest.fn(), - }, - }); - - fixture.detectChanges(); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('should throw when trying to register an action twice', () => { - const action = new BoldAction(); - comp.registerAction(action); - const registerAction = () => comp.registerAction(action); - expect(registerAction).toThrow(Error); - }); - - it.each([ - { - action: new AttachmentAction(), - text: 'Attachment', - url: 'https://test.invalid/img.png', - defaultText: AttachmentAction.DEFAULT_INSERT_TEXT, - }, - { - action: new UrlAction(), - text: 'Link', - url: 'https://test.invalid/', - defaultText: UrlAction.DEFAULT_INSERT_TEXT, - }, - ])('should insert $text', ({ action, text, url, defaultText }: { action: UrlAction | AttachmentAction; text: string; url: string; defaultText: string }) => { - const prefix = text === 'Attachment' ? '!' : ''; - comp.registerAction(action); - action.executeInCurrentEditor({ text, url }); - expect(comp.getText()).toBe(`${prefix}[${text}](${url})`); - // No arguments -> insert default text - comp.setText(''); - action.executeInCurrentEditor(); - expect(comp.getText()).toBe(defaultText); - }); - - it('should not access the clipboard if no upload callback is specified', async () => { - const clipboardReadSpy = jest.spyOn(navigator.clipboard, 'read'); - const addPasteListenerSpy = jest.spyOn(comp['textEditorAdapter'], 'addPasteListener'); - const action = new AttachmentAction(); - comp.registerAction(action); - // The addPasteListenerSpy should have received a function that does not result in the clipboard being read when called. - expect(addPasteListenerSpy).toHaveBeenCalled(); - const pasteListener = addPasteListenerSpy.mock.calls[0][0]; - expect(pasteListener).toBeDefined(); - await pasteListener(''); - expect(clipboardReadSpy).not.toHaveBeenCalled(); - }); - - it('should process files from the clipboard', async () => { - const imageBlob = new Blob([]); - const imageClipboardItem: MockClipboardItem = { - types: ['image/png'], - getType: jest.fn().mockResolvedValue(imageBlob), - presentationStyle: 'inline', - }; - - const nonImageBlob = new Blob(['Sample text content']); - const textClipboardItem: MockClipboardItem = { - types: ['text/plain'], - getType: jest.fn().mockResolvedValue(nonImageBlob), - presentationStyle: 'inline', - }; - - // Mock the clipboard read function to return the created ClipboardItems - const clipboardReadSpy = jest.spyOn(navigator.clipboard, 'read').mockResolvedValue([imageClipboardItem, textClipboardItem]); - const addPasteListenerSpy = jest.spyOn(comp['textEditorAdapter'], 'addPasteListener'); - const uploadCallback = jest.fn(); - const action = new AttachmentAction(); - action.setUploadCallback(uploadCallback); - comp.registerAction(action); - const pasteListener = addPasteListenerSpy.mock.calls[0][0]; - expect(pasteListener).toBeDefined(); - await pasteListener(''); - expect(clipboardReadSpy).toHaveBeenCalledOnce(); - expect(uploadCallback).toHaveBeenCalledExactlyOnceWith([new File([imageBlob], 'image.png', { type: 'image/png' })]); - }); - - it('should insert unordered list', () => { - const action = new UnorderedListAction(); - comp.registerAction(action); - action.executeInCurrentEditor(); - expect(comp.getText()).toBe('- '); - }); - - it('should toggle unordered list, skipping empty lines', () => { - const action = new UnorderedListAction(); - comp.registerAction(action); - const lines = ['One', '', 'Two', 'Three']; - const bulletedLines = lines.map((line) => (line ? `- ${line}` : '')); - comp.setText(lines.join('\n')); - comp.setSelection({ - startLineNumber: 1, - startColumn: 1, - endLineNumber: lines.length, - endColumn: lines[lines.length - 1].length + 1, - }); - // Introduce list - action.executeInCurrentEditor(); - expect(comp.getText()).toBe(bulletedLines.join('\n')); - // Remove list - action.executeInCurrentEditor(); - expect(comp.getText()).toBe(lines.join('\n')); - }); - - it('should insert ordered list', () => { - const action = new OrderedListAction(); - comp.registerAction(action); - action.executeInCurrentEditor(); - expect(comp.getText()).toBe('1. '); - }); - - it.each([1, 2, 3])('Should toggle heading %i on selected line', (headingLevel) => { - const action = new HeadingAction(headingLevel); - comp.registerAction(action); - // No selection -> insert heading - action.executeInCurrentEditor(); - expect(comp.getText()).toBe(`${'#'.repeat(headingLevel)} Heading ${headingLevel}`); - // Selection -> toggle heading - comp.setSelection({ startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: comp.getText().length + 1 }); - action.executeInCurrentEditor(); - expect(comp.getText()).toBe(`Heading ${headingLevel}`); - action.executeInCurrentEditor(); - expect(comp.getText()).toBe(`${'#'.repeat(headingLevel)} Heading ${headingLevel}`); - }); - - it('should insert test case names', () => { - const action = new TestCaseAction(); - const testCaseName = 'testCase()'; - action.values = [{ value: testCaseName, id: '1' }]; - // With specified test case - comp.registerAction(action); - action.executeInCurrentEditor({ selectedItem: action.values[0] }); - expect(comp.getText()).toBe(testCaseName); - // Without specified test case - comp.setText(''); - action.executeInCurrentEditor(); - expect(comp.getText()).toBe(TestCaseAction.DEFAULT_INSERT_TEXT); - }); - - it('should throw when trying to register a completer without a model', () => { - const action = new TestCaseAction(); - const registerAction = () => comp.registerAction(action); - // Detach model (should not happen in practice) - comp['_editor'].setModel(null); - expect(registerAction).toThrow(Error); - }); - - it('should provide test case completions', async () => { - comp.changeModel('testCase', '', 'custom-md'); - const model = comp.models[0]; - const action = new TestCaseAction(); - action.values = [ - { value: 'testCase1', id: '1' }, - { value: 'testCase2', id: '2' }, - ]; - const registerCompletionProviderStub = jest.spyOn(monaco.languages, 'registerCompletionItemProvider').mockImplementation(); - comp.registerAction(action); - expect(registerCompletionProviderStub).toHaveBeenCalledOnce(); - const completionFunction = registerCompletionProviderStub.mock.calls[0][1].provideCompletionItems; - expect(completionFunction).toBeDefined(); - // We do not use completionContext and cancellationToken, but they are required by the function signature. Therefore, we pass empty objects. - const completionList = await completionFunction(model, new monaco.Position(1, 1), {} as monaco.languages.CompletionContext, {} as monaco.CancellationToken); - const suggestions = (completionList as monaco.languages.CompletionList)!.suggestions; - expect(suggestions).toHaveLength(2); - expect(suggestions[0].label).toBe(action.values[0].value); - expect(suggestions[1].label).toBe(action.values[1].value); - - // The completion provider should only provide completions for the current model. - comp.changeModel('other', '', 'custom-md'); - const otherModel = comp.models[1]; - const completionListOther = await completionFunction(otherModel, new monaco.Position(1, 1), {} as monaco.languages.CompletionContext, {} as monaco.CancellationToken); - expect(completionListOther).toBeUndefined(); - }); - - it('should insert tasks', () => { - const action = new TaskAction(); - comp.registerAction(action); - action.executeInCurrentEditor(); - expect(comp.getText()).toBe(`[task]${TaskAction.TEXT}`); - }); - - it('should enter fullscreen', () => { - const action = new FullscreenAction(); - comp.registerAction(action); - const enterFullscreenStub = jest.spyOn(FullscreenUtil, 'enterFullscreen').mockImplementation(); - jest.spyOn(FullscreenUtil, 'isFullScreen').mockReturnValue(false); - const dummyElement = document.createElement('div'); - action.element = dummyElement; - action.executeInCurrentEditor(); - expect(enterFullscreenStub).toHaveBeenCalledExactlyOnceWith(dummyElement); - - // Without a specified element, it should use the editor's DOM node - action.element = undefined; - action.executeInCurrentEditor(); - const editorElement = document.querySelector('.monaco-editor'); - expect(enterFullscreenStub).toHaveBeenCalledTimes(2); - expect(enterFullscreenStub).toHaveBeenNthCalledWith(2, editorElement); - }); - - it('should leave fullscreen', () => { - const action = new FullscreenAction(); - comp.registerAction(action); - const exitFullscreenStub = jest.spyOn(FullscreenUtil, 'exitFullscreen').mockImplementation(); - jest.spyOn(FullscreenUtil, 'isFullScreen').mockReturnValue(true); - action.executeInCurrentEditor(); - expect(exitFullscreenStub).toHaveBeenCalledOnce(); - }); - - it.each([ - { - action: new BoldAction(), - textWithoutDelimiters: 'Here is some bold text.', - textWithDelimiters: '**Here is some bold text.**', - }, - { - action: new ItalicAction(), - textWithoutDelimiters: 'Here is some italic text.', - textWithDelimiters: '*Here is some italic text.*', - }, - { - action: new UnderlineAction(), - textWithoutDelimiters: 'Here is some underlined text.', - textWithDelimiters: 'Here is some underlined text.', - }, - { - action: new CodeAction(), - textWithoutDelimiters: 'Here is some code.', - textWithDelimiters: '`Here is some code.`', - }, - { - action: new ColorAction(), - textWithoutDelimiters: 'Here is some blue.', - textWithDelimiters: 'Here is some blue.', - actionArgs: { color: 'blue' }, - }, - { - action: new ColorAction(), - textWithoutDelimiters: 'Here is some red.', - textWithDelimiters: 'Here is some red.', // No argument -> default color is red - }, - { - action: new CodeBlockAction(), - textWithoutDelimiters: 'public void main() { }', - textWithDelimiters: '```\npublic void main() { }\n```', - }, - { - action: new CodeBlockAction('java'), - textWithoutDelimiters: 'public void main() { }', - textWithDelimiters: '```java\npublic void main() { }\n```', - }, - { - action: new QuoteAction(), - initialText: '> ', - textToType: 'some quoted text', - textWithDelimiters: '> some quoted text', - textWithoutDelimiters: 'some quoted text', - }, - { - action: new FormulaAction(), - initialText: `$$ ${FormulaAction.DEFAULT_FORMULA} $$`, - textToType: '+ 42x', - textWithDelimiters: `$$ ${FormulaAction.DEFAULT_FORMULA}+ 42x $$`, - textWithoutDelimiters: `${FormulaAction.DEFAULT_FORMULA}+ 42x`, - }, - ])( - 'Delimiter action ($action.id) should insert delimiters at position and toggle around selection', - ({ - action, - textWithoutDelimiters, - textWithDelimiters, - actionArgs, - textToType, - initialText, - }: { - action: TextEditorAction; - textWithoutDelimiters: string; - textWithDelimiters: string; - actionArgs?: object; - textToType?: string; - initialText?: string; - }) => { - testDelimiterAction(action, textWithoutDelimiters, textWithDelimiters, actionArgs, textToType, initialText); - }, - ); - - /** - * Test the action by inserting delimiters at position and toggling around selection. - * @param action The action to test - * @param textWithoutDelimiters The text without delimiters (e.g. 'Here is some bold text.') - * @param textWithDelimiters The text with delimiters (e.g. '**Here is some bold text.**') - * @param actionArgs The argument to pass to the action - * @param textToType The text to type after the action has been triggered without a selection. - * @param initialText The initial text to expect after the action has been triggered without a selection. - */ - function testDelimiterAction( - action: TextEditorAction, - textWithoutDelimiters: string, - textWithDelimiters: string, - actionArgs?: object, - textToType?: string, - initialText?: string, - ): void { - const runSpy = jest.spyOn(action, 'run'); - comp.registerAction(action); - // Position - action.executeInCurrentEditor(actionArgs); - if (initialText) { - expect(comp.getText()).toBe(initialText); - } - comp.triggerKeySequence(textToType ?? textWithoutDelimiters); - const text = comp.getText(); - expect(text).toBe(textWithDelimiters); - // Selection - const textLines = text.split('\n'); - const fullSelection = { - startLineNumber: 1, - startColumn: 1, - endLineNumber: textLines.length, - endColumn: textLines[textLines.length - 1].length + 1, - }; - comp.setSelection(fullSelection); - // Toggle off - action.executeInCurrentEditor(actionArgs); - expect(comp.getText()).toBe(textWithoutDelimiters); - // Toggle on - action.executeInCurrentEditor(actionArgs); - expect(comp.getText()).toBe(textWithDelimiters); - expect(runSpy).toHaveBeenCalledTimes(3); - } -}); diff --git a/src/test/javascript/spec/integration/monaco-editor/monaco-editor-communication-action.integration.spec.ts b/src/test/javascript/spec/integration/monaco-editor/monaco-editor-communication-action.integration.spec.ts deleted file mode 100644 index 673e41e12b80..000000000000 --- a/src/test/javascript/spec/integration/monaco-editor/monaco-editor-communication-action.integration.spec.ts +++ /dev/null @@ -1,470 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MockProvider } from 'ng-mocks'; -import { MetisService } from 'app/communication/service/metis.service'; -import { LectureService } from 'app/lecture/manage/services/lecture.service'; -import { HttpResponse } from '@angular/common/http'; -import { of } from 'rxjs'; -import { CourseManagementService } from 'app/core/course/manage/services/course-management.service'; -import { ChannelService } from 'app/communication/conversations/service/channel.service'; -import { MockMetisService } from '../../helpers/mocks/service/mock-metis-service.service'; -import { MockTranslateService } from '../../helpers/mocks/service/mock-translate.service'; -import { TranslateService } from '@ngx-translate/core'; -import { MockLocalStorageService } from '../../helpers/mocks/service/mock-local-storage.service'; -import { LocalStorageService } from 'ngx-webstorage'; -import { MockResizeObserver } from '../../helpers/mocks/service/mock-resize-observer'; -import { ChannelReferenceAction } from 'app/shared/monaco-editor/model/actions/communication/channel-reference.action'; -import { UserMentionAction } from 'app/shared/monaco-editor/model/actions/communication/user-mention.action'; -import { ExerciseReferenceAction } from 'app/shared/monaco-editor/model/actions/communication/exercise-reference.action'; -import { metisExamChannelDTO, metisExerciseChannelDTO, metisGeneralChannelDTO, metisTutor, metisUser1, metisUser2 } from '../../helpers/sample/metis-sample-data'; -import { TextEditorAction } from 'app/shared/monaco-editor/model/actions/text-editor-action.model'; -import * as monaco from 'monaco-editor'; -import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; -import { User } from 'app/core/user/user.model'; -import { Exercise } from 'app/exercise/shared/entities/exercise/exercise.model'; -import { Lecture } from 'app/lecture/shared/entities/lecture.model'; -import { LectureAttachmentReferenceAction } from 'app/shared/monaco-editor/model/actions/communication/lecture-attachment-reference.action'; -import { LectureUnitType } from 'app/lecture/shared/entities/lecture-unit/lectureUnit.model'; -import { ReferenceType } from 'app/communication/metis.util'; -import { Attachment } from 'app/lecture/shared/entities/attachment.model'; -import dayjs from 'dayjs/esm'; -import { FaqReferenceAction } from 'app/shared/monaco-editor/model/actions/communication/faq-reference.action'; -import { Faq } from 'app/communication/shared/entities/faq.model'; -import { MockFileService } from '../../helpers/mocks/service/mock-file.service'; -import { FileService } from 'app/shared/service/file.service'; -import { ChannelIdAndNameDTO } from 'app/communication/shared/entities/conversation/channel.model'; - -describe('MonacoEditorCommunicationActionIntegration', () => { - let comp: MonacoEditorComponent; - let fixture: ComponentFixture; - let metisService: MetisService; - let fileService: FileService; - let courseManagementService: CourseManagementService; - let channelService: ChannelService; - let lectureService: LectureService; - let provider: monaco.languages.CompletionItemProvider; - - // Actions - let channelReferenceAction: ChannelReferenceAction; - let userMentionAction: UserMentionAction; - let exerciseReferenceAction: ExerciseReferenceAction; - let faqReferenceAction: FaqReferenceAction; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [MonacoEditorComponent], - providers: [ - { provide: MetisService, useClass: MockMetisService }, - { provide: FileService, useClass: MockFileService }, - { provide: TranslateService, useClass: MockTranslateService }, - { provide: LocalStorageService, useClass: MockLocalStorageService }, - MockProvider(LectureService), - MockProvider(CourseManagementService), - MockProvider(ChannelService), - ], - }).compileComponents(); - - global.ResizeObserver = jest.fn().mockImplementation((callback: ResizeObserverCallback) => { - return new MockResizeObserver(callback); - }); - fixture = TestBed.createComponent(MonacoEditorComponent); - comp = fixture.componentInstance; - metisService = TestBed.inject(MetisService); - fileService = TestBed.inject(FileService); - courseManagementService = TestBed.inject(CourseManagementService); - lectureService = TestBed.inject(LectureService); - channelService = TestBed.inject(ChannelService); - channelReferenceAction = new ChannelReferenceAction(metisService, channelService); - userMentionAction = new UserMentionAction(courseManagementService, metisService); - exerciseReferenceAction = new ExerciseReferenceAction(metisService); - faqReferenceAction = new FaqReferenceAction(metisService); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - const registerActionWithCompletionProvider = (action: TextEditorAction, triggerCharacter?: string) => { - const registerCompletionProviderStub = jest.spyOn(monaco.languages, 'registerCompletionItemProvider').mockImplementation(); - comp.registerAction(action); - expect(registerCompletionProviderStub).toHaveBeenCalledOnce(); - provider = registerCompletionProviderStub.mock.calls[0][1]; - expect(provider).toBeDefined(); - expect(provider.provideCompletionItems).toBeDefined(); - if (triggerCharacter) { - expect(provider.triggerCharacters).toContain(triggerCharacter); - } - }; - - describe.each([ - { actionId: ChannelReferenceAction.ID, defaultInsertText: '#', triggerCharacter: '#' }, - { actionId: UserMentionAction.ID, defaultInsertText: '@', triggerCharacter: '@' }, - { actionId: ExerciseReferenceAction.ID, defaultInsertText: '/exercise', triggerCharacter: '/' }, - { actionId: FaqReferenceAction.ID, defaultInsertText: '/faq', triggerCharacter: '/' }, - ])('Suggestions and default behavior for $actionId', ({ actionId, defaultInsertText, triggerCharacter }) => { - let action: ChannelReferenceAction | UserMentionAction | ExerciseReferenceAction | FaqReferenceAction; - let channels: ChannelIdAndNameDTO[]; - let users: User[]; - let exercises: Exercise[]; - let faqs: Faq[]; - - beforeEach(() => { - fixture.detectChanges(); - comp.changeModel('initial', ''); - channels = [metisGeneralChannelDTO, metisExamChannelDTO, metisExerciseChannelDTO]; - channelReferenceAction.cachedChannels = channels; - users = [metisUser1, metisUser2, metisTutor]; - jest.spyOn(courseManagementService, 'searchMembersForUserMentions').mockReturnValue(of(new HttpResponse({ body: users, status: 200 }))); - exercises = metisService.getCourse().exercises!; - faqs = metisService.getCourse().faqs!; - - switch (actionId) { - case ChannelReferenceAction.ID: - action = channelReferenceAction; - break; - case UserMentionAction.ID: - action = userMentionAction; - break; - case ExerciseReferenceAction.ID: - action = exerciseReferenceAction; - break; - case FaqReferenceAction.ID: - action = faqReferenceAction; - break; - } - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('should suggest no values for the wrong model', async () => { - registerActionWithCompletionProvider(action, triggerCharacter); - comp.changeModel('other', '#ch'); - const suggestions = await provider.provideCompletionItems(comp.models[1], new monaco.Position(1, 4), {} as any, {} as any); - expect(suggestions).toBeUndefined(); - }); - - it('should suggest no values if the user is not typing a reference', async () => { - comp.setText('some text that is no reference'); - registerActionWithCompletionProvider(action, triggerCharacter); - const providerResult = await provider.provideCompletionItems(comp.models[0], new monaco.Position(1, 4), {} as any, {} as any); - expect(providerResult).toBeUndefined(); - }); - - it('should insert the correct default text when executed', () => { - registerActionWithCompletionProvider(action, triggerCharacter); - action.executeInCurrentEditor(); - expect(comp.getText()).toBe(defaultInsertText); - }); - - const checkChannelSuggestions = (suggestions: monaco.languages.CompletionItem[], channels: ChannelIdAndNameDTO[]) => { - expect(suggestions).toHaveLength(channels.length); - suggestions.forEach((suggestion, index) => { - expect(suggestion.label).toBe(`#${channels[index].name}`); - expect(suggestion.insertText).toBe(`[channel]${channels[index].name}(${channels[index].id})[/channel]`); - expect(suggestion.detail).toBe(action.label); - }); - }; - - const checkUserSuggestions = (suggestions: monaco.languages.CompletionItem[], users: User[]) => { - expect(suggestions).toHaveLength(users.length); - suggestions.forEach((suggestion, index) => { - expect(suggestion.label).toBe(`@${users[index].name}`); - expect(suggestion.insertText).toBe(`[user]${users[index].name}(${users[index].login})[/user]`); - expect(suggestion.detail).toBe(action.label); - }); - }; - - const checkExerciseSuggestions = (suggestions: monaco.languages.CompletionItem[], exercises: Exercise[]) => { - expect(suggestions).toHaveLength(exercises.length); - suggestions.forEach((suggestion, index) => { - expect(suggestion.label).toBe(`/exercise ${exercises[index].title}`); - expect(suggestion.insertText).toBe( - `[${exercises[index].type}]${exercises[index].title}(${metisService.getLinkForExercise(exercises[index].id!.toString())})[/${exercises[index].type}]`, - ); - expect(suggestion.detail).toBe(exercises[index].type); - }); - }; - - const checkFaqSuggestions = (suggestions: monaco.languages.CompletionItem[], faqs: Faq[]) => { - expect(suggestions).toHaveLength(faqs.length); - suggestions.forEach((suggestion, index) => { - expect(suggestion.label).toBe(`/faq ${faqs[index].questionTitle}`); - expect(suggestion.insertText).toBe(`[faq]${faqs[index].questionTitle}(${metisService.getLinkForFaq()}?faqId=${faqs[index].id})[/faq]`); - expect(suggestion.detail).toBe('faq'); - }); - }; - - it.each(['', 'ex'])('should suggest the correct values if the user is typing a reference (suffix "%s")', async (referenceSuffix: string) => { - const reference = triggerCharacter + referenceSuffix; - comp.setText(reference); - const column = reference.length + 1; - registerActionWithCompletionProvider(action, triggerCharacter); - const providerResult = await provider.provideCompletionItems(comp.models[0], new monaco.Position(1, column), {} as any, {} as any); - expect(providerResult).toBeDefined(); - expect(providerResult!.incomplete).toBe(actionId === UserMentionAction.ID); - const suggestions = providerResult!.suggestions; - switch (actionId) { - case ChannelReferenceAction.ID: - checkChannelSuggestions(suggestions, channels); - break; - case UserMentionAction.ID: - checkUserSuggestions(suggestions, users); - break; - case ExerciseReferenceAction.ID: - checkExerciseSuggestions(suggestions, exercises); - break; - case FaqReferenceAction.ID: - checkFaqSuggestions(suggestions, faqs); - break; - } - }); - }); - - describe('ChannelReferenceAction', () => { - it('should use cached channels if available', async () => { - const channels: ChannelIdAndNameDTO[] = [metisGeneralChannelDTO, metisExamChannelDTO, metisExerciseChannelDTO]; - channelReferenceAction.cachedChannels = channels; - const getChannelsSpy = jest.spyOn(channelService, 'getPublicChannelsOfCourse'); - fixture.detectChanges(); - comp.registerAction(channelReferenceAction); - expect(await channelReferenceAction.fetchChannels()).toBe(channels); - expect(getChannelsSpy).not.toHaveBeenCalled(); - }); - - it('should load and cache channels if none are cached', async () => { - const channels: ChannelIdAndNameDTO[] = [metisGeneralChannelDTO, metisExamChannelDTO, metisExerciseChannelDTO]; - const getChannelsStub = jest.spyOn(channelService, 'getPublicChannelsOfCourse').mockReturnValue(of(new HttpResponse({ body: channels, status: 200 }))); - fixture.detectChanges(); - comp.registerAction(channelReferenceAction); - expect(await channelReferenceAction.fetchChannels()).toBe(channels); - expect(getChannelsStub).toHaveBeenCalledExactlyOnceWith(metisService.getCourse().id!); - expect(channelReferenceAction.cachedChannels).toBe(channels); - }); - - it('should insert # for channel references', () => { - fixture.detectChanges(); - comp.registerAction(channelReferenceAction); - channelReferenceAction.executeInCurrentEditor(); - expect(comp.getText()).toBe('#'); - }); - }); - - describe('ExerciseReferenceAction (edge cases)', () => { - it('should initialize with empty values if exercises are not available', () => { - jest.spyOn(metisService, 'getCourse').mockReturnValue({ exercises: undefined } as any); - fixture.detectChanges(); - comp.registerAction(exerciseReferenceAction); - expect(exerciseReferenceAction.getValues()).toEqual([]); - }); - - it('should insert / for faq references', () => { - fixture.detectChanges(); - comp.registerAction(faqReferenceAction); - faqReferenceAction.executeInCurrentEditor(); - expect(comp.getText()).toBe('/faq'); - }); - }); - - describe('FaqReferenceAction', () => { - it('should initialize with empty values if faqs are not available', () => { - jest.spyOn(metisService, 'getCourse').mockReturnValue({ faqs: undefined } as any); - - fixture.detectChanges(); - comp.registerAction(faqReferenceAction); - expect(faqReferenceAction.getValues()).toEqual([]); - }); - }); - - describe('LectureAttachmentReferenceAction', () => { - let lectures: Lecture[]; - let lectureAttachmentReferenceAction: LectureAttachmentReferenceAction; - - beforeEach(() => { - lectures = metisService.getCourse().lectures!; - jest.spyOn(lectureService, 'findAllByCourseIdWithSlides').mockReturnValue(of(new HttpResponse({ body: lectures, status: 200 }))); - lectureAttachmentReferenceAction = new LectureAttachmentReferenceAction(metisService, lectureService, fileService); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('should correctly initialize lecturesWithDetails', () => { - fixture.detectChanges(); - comp.registerAction(lectureAttachmentReferenceAction); - - const lecturesWithDetails = lectures.map((lecture) => ({ - id: lecture.id!, - title: lecture.title!, - attachmentUnits: lecture.lectureUnits?.filter((unit) => unit.type === LectureUnitType.ATTACHMENT), - attachments: lecture.attachments?.map((attachment) => ({ - ...attachment, - link: attachment.link && attachment.name ? fileService.createAttachmentFileUrl(attachment.link, attachment.name, false) : attachment.link, - linkUrl: attachment.link && attachment.name ? 'api/core/files/' + fileService.createAttachmentFileUrl(attachment.link, attachment.name, true) : attachment.link, - })), - })); - - expect(lectureAttachmentReferenceAction.lecturesWithDetails).toEqual(lecturesWithDetails); - }); - - it('should error on unsupported reference type', () => { - fixture.detectChanges(); - comp.registerAction(lectureAttachmentReferenceAction); - const executeAction = () => - lectureAttachmentReferenceAction.executeInCurrentEditor({ reference: ReferenceType.PROGRAMMING, lecture: lectureAttachmentReferenceAction.lecturesWithDetails[0] }); - expect(executeAction).toThrow(Error); - }); - - it('should reference a lecture', () => { - fixture.detectChanges(); - comp.registerAction(lectureAttachmentReferenceAction); - const lecture = lectureAttachmentReferenceAction.lecturesWithDetails[0]; - lectureAttachmentReferenceAction.executeInCurrentEditor({ reference: ReferenceType.LECTURE, lecture }); - expect(comp.getText()).toBe(`[lecture]${lecture.title}(${metisService.getLinkForLecture(lecture.id.toString())})[/lecture]`); - }); - - it('should reference an attachment without brackets', () => { - fixture.detectChanges(); - - const attachmentNameWithBrackets = 'Test (File) With [Brackets] And (More) [Bracket(s)]'; - const attachmentNameWithoutBrackets = 'Test File With Brackets And More Brackets'; - - const newAttachment = { - id: 53, - name: attachmentNameWithBrackets, - link: '/api/core/files/attachments/lecture/4/Mein_Test_PDF3.pdf', - version: 1, - uploadDate: dayjs('2019-05-07T08:49:59+02:00'), - attachmentType: 'FILE', - } as Attachment; - - comp.registerAction(lectureAttachmentReferenceAction); - const lecture = lectureAttachmentReferenceAction.lecturesWithDetails[0]; - const shortLink = newAttachment.link?.split('attachments/')[1]; - lectureAttachmentReferenceAction.executeInCurrentEditor({ reference: ReferenceType.ATTACHMENT, lecture: lecture, attachment: newAttachment }); - expect(comp.getText()).toBe(`[attachment]${attachmentNameWithoutBrackets}(${shortLink})[/attachment]`); - }); - - it('should reference a lecture without brackets', () => { - fixture.detectChanges(); - - const lectureNameWithBrackets = 'Test (Lecture) With [Brackets] And (More) [Bracket(s)]'; - const lectureNameWithoutBrackets = 'Test Lecture With Brackets And More Brackets'; - - comp.registerAction(lectureAttachmentReferenceAction); - const lecture = lectureAttachmentReferenceAction.lecturesWithDetails[0]; - const previousTitle = lecture.title; - lecture.title = lectureNameWithBrackets; - lectureAttachmentReferenceAction.executeInCurrentEditor({ reference: ReferenceType.LECTURE, lecture }); - lecture.title = previousTitle; - expect(comp.getText()).toBe(`[lecture]${lectureNameWithoutBrackets}(${metisService.getLinkForLecture(lecture.id.toString())})[/lecture]`); - }); - - it('should reference an attachment unit without brackets', () => { - fixture.detectChanges(); - - const attachmentUnitNameWithBrackets = 'Test (AttachmentUnit) With [Brackets] And (More) [Bracket(s)]'; - const attachmentUnitNameWithoutBrackets = 'Test AttachmentUnit With Brackets And More Brackets'; - - comp.registerAction(lectureAttachmentReferenceAction); - const lecture = lectureAttachmentReferenceAction.lecturesWithDetails[2]; - const attachmentUnit = lecture.attachmentUnits![0]; - const previousName = attachmentUnit.name; - attachmentUnit.name = attachmentUnitNameWithBrackets; - const attachmentUnitFileName = 'Metis-Attachment.pdf'; - lectureAttachmentReferenceAction.executeInCurrentEditor({ - reference: ReferenceType.ATTACHMENT_UNITS, - lecture, - attachmentUnit, - }); - attachmentUnit.name = previousName; - expect(comp.getText()).toBe(`[lecture-unit]${attachmentUnitNameWithoutBrackets}(${attachmentUnitFileName})[/lecture-unit]`); - }); - - it('should reference an attachment', () => { - fixture.detectChanges(); - comp.registerAction(lectureAttachmentReferenceAction); - const lecture = lectureAttachmentReferenceAction.lecturesWithDetails[0]; - const attachment = lecture.attachments![0]; - const attachmentFileName = 'Metis-Attachment.pdf'; - lectureAttachmentReferenceAction.executeInCurrentEditor({ - reference: ReferenceType.ATTACHMENT, - lecture, - attachment, - }); - expect(comp.getText()).toBe(`[attachment]${attachment.name}(${attachmentFileName})[/attachment]`); - }); - - it('should error when trying to reference a nonexistent attachment', () => { - fixture.detectChanges(); - comp.registerAction(lectureAttachmentReferenceAction); - const lecture = lectureAttachmentReferenceAction.lecturesWithDetails[0]; - const executeAction = () => - lectureAttachmentReferenceAction.executeInCurrentEditor({ - reference: ReferenceType.ATTACHMENT, - lecture, - attachment: undefined, - }); - expect(executeAction).toThrow(Error); - }); - - it('should reference an attachment unit', () => { - fixture.detectChanges(); - comp.registerAction(lectureAttachmentReferenceAction); - const lecture = lectureAttachmentReferenceAction.lecturesWithDetails[2]; - const attachmentUnit = lecture.attachmentUnits![0]; - const attachmentUnitFileName = 'Metis-Attachment.pdf'; - lectureAttachmentReferenceAction.executeInCurrentEditor({ - reference: ReferenceType.ATTACHMENT_UNITS, - lecture, - attachmentUnit, - }); - expect(comp.getText()).toBe(`[lecture-unit]${attachmentUnit.name}(${attachmentUnitFileName})[/lecture-unit]`); - }); - - it('should error when trying to reference a nonexistent attachment unit', () => { - fixture.detectChanges(); - comp.registerAction(lectureAttachmentReferenceAction); - const lecture = lectureAttachmentReferenceAction.lecturesWithDetails[0]; - const executeAction = () => - lectureAttachmentReferenceAction.executeInCurrentEditor({ - reference: ReferenceType.ATTACHMENT_UNITS, - lecture, - attachmentUnit: undefined, - }); - expect(executeAction).toThrow(Error); - }); - - it('should reference a slide', () => { - fixture.detectChanges(); - comp.registerAction(lectureAttachmentReferenceAction); - const lecture = lectureAttachmentReferenceAction.lecturesWithDetails[2]; - const attachmentUnit = lecture.attachmentUnits![0]; - const slide = attachmentUnit.slides![0]; - const slideLink = 'slides'; - lectureAttachmentReferenceAction.executeInCurrentEditor({ - reference: ReferenceType.SLIDE, - lecture, - attachmentUnit, - slide, - }); - expect(comp.getText()).toBe(`[slide]${attachmentUnit.name} Slide ${slide.slideNumber}(${slideLink})[/slide]`); - }); - - it('should error when incorrectly referencing a slide', () => { - fixture.detectChanges(); - comp.registerAction(lectureAttachmentReferenceAction); - const lecture = lectureAttachmentReferenceAction.lecturesWithDetails[2]; - const attachmentUnit = lecture.attachmentUnits![0]; - const executeAction = () => - lectureAttachmentReferenceAction.executeInCurrentEditor({ - reference: ReferenceType.SLIDE, - lecture, - attachmentUnit, - slide: undefined, - }); - expect(executeAction).toThrow(Error); - }); - }); -}); diff --git a/src/test/javascript/spec/integration/monaco-editor/monaco-editor-grading-instructions.integration.spec.ts b/src/test/javascript/spec/integration/monaco-editor/monaco-editor-grading-instructions.integration.spec.ts deleted file mode 100644 index 71890fd24f8a..000000000000 --- a/src/test/javascript/spec/integration/monaco-editor/monaco-editor-grading-instructions.integration.spec.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; -import { MockResizeObserver } from '../../helpers/mocks/service/mock-resize-observer'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { GradingInstructionAction } from 'app/shared/monaco-editor/model/actions/grading-criteria/grading-instruction.action'; -import { GradingCreditsAction } from 'app/shared/monaco-editor/model/actions/grading-criteria/grading-credits.action'; -import { GradingScaleAction } from 'app/shared/monaco-editor/model/actions/grading-criteria/grading-scale.action'; -import { GradingDescriptionAction } from 'app/shared/monaco-editor/model/actions/grading-criteria/grading-description.action'; -import { GradingFeedbackAction } from 'app/shared/monaco-editor/model/actions/grading-criteria/grading-feedback.action'; -import { GradingUsageCountAction } from 'app/shared/monaco-editor/model/actions/grading-criteria/grading-usage-count.action'; -import { GradingCriterionAction } from 'app/shared/monaco-editor/model/actions/grading-criteria/grading-criterion.action'; -import { MockThemeService } from '../../helpers/mocks/service/mock-theme.service'; -import { ThemeService } from 'app/core/theme/shared/theme.service'; -import { MockTranslateService } from '../../helpers/mocks/service/mock-translate.service'; -import { TranslateService } from '@ngx-translate/core'; - -describe('MonacoEditorActionGradingInstructionsIntegration', () => { - let fixture: ComponentFixture; - let comp: MonacoEditorComponent; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [MonacoEditorComponent], - providers: [ - { provide: ThemeService, useClass: MockThemeService }, - { provide: TranslateService, useClass: MockTranslateService }, - ], - }) - .compileComponents() - .then(() => { - fixture = TestBed.createComponent(MonacoEditorComponent); - comp = fixture.componentInstance; - global.ResizeObserver = jest.fn().mockImplementation((callback: ResizeObserverCallback) => { - return new MockResizeObserver(callback); - }); - fixture.detectChanges(); - }); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - const setupActions = () => { - const creditsAction = new GradingCreditsAction(); - const scaleAction = new GradingScaleAction(); - const descriptionAction = new GradingDescriptionAction(); - const feedbackAction = new GradingFeedbackAction(); - const usageCountAction = new GradingUsageCountAction(); - const instructionAction = new GradingInstructionAction(creditsAction, scaleAction, descriptionAction, feedbackAction, usageCountAction); - const criterionAction = new GradingCriterionAction(instructionAction); - [creditsAction, scaleAction, descriptionAction, feedbackAction, usageCountAction, instructionAction, criterionAction].forEach((action) => comp.registerAction(action)); - return { creditsAction, scaleAction, descriptionAction, feedbackAction, usageCountAction, instructionAction, criterionAction }; - }; - - const expectedInstructionTextWithoutCriterion = - '\n[instruction]' + - '\n\t[credits] 0' + - '\n\t[gradingScale] Add instruction grading scale here (only visible for tutors)' + - '\n\t[description] Add grading instruction here (only visible for tutors)' + - '\n\t[feedback] Add feedback for students here (visible for students)' + - '\n\t[maxCountInScore] 0'; - - const generalInstructionText = 'These are some general instructions for the tutors.'; - - it('should insert grading instructions', () => { - comp.triggerKeySequence(generalInstructionText); - const actions = setupActions(); - actions.instructionAction.executeInCurrentEditor(); - expect(comp.getText()).toBe(generalInstructionText + expectedInstructionTextWithoutCriterion); - }); - - it('should insert grading criterion', () => { - comp.triggerKeySequence(generalInstructionText); - const actions = setupActions(); - actions.criterionAction.executeInCurrentEditor(); - expect(comp.getText()).toBe(`${generalInstructionText}\n[criterion] Add criterion title (only visible to tutors)${expectedInstructionTextWithoutCriterion}`); - }); -}); diff --git a/src/test/javascript/spec/jest-test-setup.ts b/src/test/javascript/spec/jest-test-setup.ts deleted file mode 100644 index 7c4a0ed4fe46..000000000000 --- a/src/test/javascript/spec/jest-test-setup.ts +++ /dev/null @@ -1,76 +0,0 @@ -import 'app/shared/util/map.extension'; -import 'app/shared/util/string.extension'; -import 'app/shared/util/array.extension'; -import 'app/core/config/dayjs'; -import 'jest-canvas-mock'; -import 'jest-extended'; -import failOnConsole from 'jest-fail-on-console'; -import { TextDecoder, TextEncoder } from 'util'; -import { MockClipboardItem } from './helpers/mocks/service/mock-clipboard-item'; - -/* - * In the Jest configuration, we only import the basic features of monaco (editor.api.js) instead - * of the full module (editor.main.js) because of a ReferenceError in the language features of Monaco. - * The following import imports the core features of the monaco editor, but leaves out the language - * features. It contains an unchecked call to queryCommandSupported, so the function has to be set - * on the document. - */ -document.queryCommandSupported = () => false; -import 'monaco-editor/esm/vs/editor/edcore.main'; // Do not move this import. - -failOnConsole({ - shouldFailOnWarn: true, - shouldFailOnLog: true, - shouldFailOnInfo: true, -}); - -const noop = () => {}; - -const mock = () => { - let storage: { [key: string]: any } = {}; - - return { - getItem: (key: any) => (key in storage ? storage[key] : null), - setItem: (key: any, value: any) => (storage[key] = value || ''), - removeItem: (key: any) => delete storage[key], - clear: () => (storage = {}), - }; -}; - -Object.defineProperty(window, 'localStorage', { value: mock() }); -Object.defineProperty(window, 'sessionStorage', { value: mock() }); -Object.defineProperty(window, 'getComputedStyle', { - value: () => ['-webkit-appearance'], -}); - -Object.defineProperty(window, 'scrollTo', { value: noop, writable: true }); -Object.defineProperty(window, 'scroll', { value: noop, writable: true }); -Object.defineProperty(window, 'alert', { value: noop, writable: true }); - -Object.defineProperty(window, 'getComputedStyle', { - value: () => ({ - getPropertyValue: () => { - return ''; - }, - }), -}); - -Object.defineProperty(window, 'matchMedia', { - writable: true, - value: jest.fn().mockImplementation((query) => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), - removeListener: jest.fn(), - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - })), -}); - -// Prevents errors with the monaco editor tests -Object.assign(global, { TextDecoder, TextEncoder }); -// Custom language definitions load clipboardService.js, which depends on ClipboardItem. This must be mocked for the tests. -Object.assign(window.navigator, { clipboard: { writeText: () => Promise.resolve(), write: () => Promise.resolve() } }); -Object.assign(global, { ClipboardItem: jest.fn().mockImplementation(() => new MockClipboardItem()) }); diff --git a/src/test/javascript/vitest/main.component.spec.ts b/src/test/javascript/vitest/main.component.spec.ts deleted file mode 100644 index 0aa3c0a8ed53..000000000000 --- a/src/test/javascript/vitest/main.component.spec.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { render } from '@testing-library/angular'; -import { AppComponent } from '../../../main/webapp/app/app.component'; -import { AlertOverlayComponent } from 'app/core/alert/alert-overlay.component'; -import { TranslateService } from '@ngx-translate/core'; -import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; -import { ThemeService } from 'app/core/theme/shared/theme.service'; -import { ProfileService } from '../../../main/webapp/app/core/layouts/profiles/shared/profile.service'; -import { ArtemisTranslatePipe } from '../../../main/webapp/app/shared/pipes/artemis-translate.pipe'; -import { MockSyncStorage } from '../spec/helpers/mocks/service/mock-sync-storage.service'; -import { MockTranslateService } from '../spec/helpers/mocks/service/mock-translate.service'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { of } from 'rxjs'; -import { provideHttpClient } from '@angular/common/http'; -import { PageRibbonComponent } from 'app/core/layouts/profiles/page-ribbon.component'; -import { NotificationPopupComponent } from 'app/core/notification/notification-popup/notification-popup.component'; - -// Mock the initialize method -class MockThemeService { - // Required because the real ThemeService uses this method, even though it's not called in the tests - initialize(): void {} -} - -// Mock ProfileService -const mockProfileService = { - getProfileInfo: vi.fn(() => - of({ - contact: 'mock-contact', - git: { - branch: 'mock-branch', - commit: { - id: { abbrev: 'mock-commit-id' }, - user: { - name: 'test', - }, - }, - }, - }), - ), -}; - -describe('JhiMainComponent', () => { - let component: AppComponent; - let componentFixture: ComponentFixture; - let container: Element; - - beforeEach(async () => { - const { fixture, container: renderedContainer } = await render(AppComponent, { - declarations: [AlertOverlayComponent, PageRibbonComponent, NotificationPopupComponent], - providers: [ - { provide: LocalStorageService, useClass: MockSyncStorage }, - { provide: SessionStorageService, useClass: MockSyncStorage }, - { provide: TranslateService, useClass: MockTranslateService }, - { provide: ThemeService, useClass: MockThemeService }, - { provide: ProfileService, useValue: mockProfileService }, // Provide the mock ProfileService - ArtemisTranslatePipe, - provideHttpClient(), - ], - }); - - componentFixture = fixture; - component = fixture.componentInstance; - container = renderedContainer; // Save the container for querying elements - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - it('should use the initialize method of ThemeService', () => { - const themeService = TestBed.inject(ThemeService) as MockThemeService; - themeService.initialize(); - }); - - it('should display footer if there is no exam', async () => { - component.isExamStarted = false; - component.showSkeleton = true; - - const footerElement = container.querySelector('jhi-footer'); - const notificationPopup = container.querySelector('jhi-notification-popup'); - - expect(footerElement).not.toBeNull(); - expect(notificationPopup).not.toBeNull(); - }); - - it('should not display footer during an exam', async () => { - component.isExamStarted = true; - component.showSkeleton = true; - component.isTestRunExam = false; - component.isShownViaLti = false; - - componentFixture.detectChanges(); // Trigger change detection - - const notificationPopup = container.querySelector('jhi-notification-popup'); - const footerElement = container.querySelector('jhi-footer'); - - expect(notificationPopup).not.toBeNull(); - expect(footerElement).toBeNull(); - }); -}); diff --git a/src/test/javascript/vitest/vitest-setup.ts b/src/test/javascript/vitest/vitest-setup.ts deleted file mode 100644 index 756ea0ed20c4..000000000000 --- a/src/test/javascript/vitest/vitest-setup.ts +++ /dev/null @@ -1,18 +0,0 @@ -import '@analogjs/vite-plugin-angular/setup-vitest'; - -import { vi } from 'vitest'; -import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; -import { getTestBed } from '@angular/core/testing'; - -// NOTE: this avoids an es6 module issue with tslib resulting in "SyntaxError: Unexpected token 'export' -// "Module /Users/krusche/Projects/Artemis/node_modules/tslib/tslib.es6.mjs:24 seems to be an ES Module but shipped in a CommonJS package. -// You might want to create an issue to the package "tslib" asking them to ship the file in .mjs extension or add "type": "module" in their package.json -vi.mock('@fingerprintjs/fingerprintjs', () => ({ - default: { - load: async () => ({ - get: async () => ({ visitorId: 'mock-visitor-id' }), - }), - }, -})); - -getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());