diff --git a/src/main/webapp/app/detail-overview-list/components/programming-diff-report-detail/programming-diff-report-detail.component.ts b/src/main/webapp/app/detail-overview-list/components/programming-diff-report-detail/programming-diff-report-detail.component.ts
index e95dbf256ee2..94fb86848f4f 100644
--- a/src/main/webapp/app/detail-overview-list/components/programming-diff-report-detail/programming-diff-report-detail.component.ts
+++ b/src/main/webapp/app/detail-overview-list/components/programming-diff-report-detail/programming-diff-report-detail.component.ts
@@ -1,20 +1,20 @@
-import { Component, Input, OnDestroy, inject } from '@angular/core';
+import { Component, Input, OnDestroy, inject, signal } from '@angular/core';
import type { ProgrammingDiffReportDetail } from 'app/detail-overview-list/detail.model';
import { FeatureToggle } from 'app/shared/feature-toggle/feature-toggle.service';
import { ButtonSize, ButtonType, TooltipPlacement } from 'app/shared/components/button.component';
import { faCodeCompare } from '@fortawesome/free-solid-svg-icons';
import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model';
import { GitDiffReportModalComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component';
-import { GitDiffReportModule } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report.module';
import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module';
import { ArtemisSharedModule } from 'app/shared/shared.module';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { GitDiffLineStatComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component';
@Component({
selector: 'jhi-programming-diff-report-detail',
templateUrl: 'programming-diff-report-detail.component.html',
standalone: true,
- imports: [ArtemisSharedModule, ArtemisSharedComponentModule, GitDiffReportModule],
+ imports: [ArtemisSharedModule, ArtemisSharedComponentModule, GitDiffLineStatComponent],
})
export class ProgrammingDiffReportDetailComponent implements OnDestroy {
protected readonly FeatureToggle = FeatureToggle;
@@ -38,7 +38,7 @@ export class ProgrammingDiffReportDetailComponent implements OnDestroy {
return;
}
- this.modalRef = this.modalService.open(GitDiffReportModalComponent, { windowClass: 'diff-view-modal' });
- this.modalRef.componentInstance.report = gitDiff;
+ this.modalRef = this.modalService.open(GitDiffReportModalComponent, { windowClass: GitDiffReportModalComponent.WINDOW_CLASS });
+ this.modalRef.componentInstance.report = signal(gitDiff);
}
}
diff --git a/src/main/webapp/app/detail-overview-list/detail.module.ts b/src/main/webapp/app/detail-overview-list/detail.module.ts
index 28a1f3a8abb9..c661ae788647 100644
--- a/src/main/webapp/app/detail-overview-list/detail.module.ts
+++ b/src/main/webapp/app/detail-overview-list/detail.module.ts
@@ -6,7 +6,6 @@ import { ArtemisSharedComponentModule } from 'app/shared/components/shared-compo
import { SubmissionResultStatusModule } from 'app/overview/submission-result-status.module';
import { ArtemisProgrammingExerciseStatusModule } from 'app/exercises/programming/manage/status/programming-exercise-status.module';
import { ArtemisProgrammingExerciseActionsModule } from 'app/exercises/programming/shared/actions/programming-exercise-actions.module';
-import { GitDiffReportModule } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report.module';
import { ArtemisProgrammingExerciseInstructionsEditorModule } from 'app/exercises/programming/manage/instructions-editor/programming-exercise-instructions-editor.module';
import { ArtemisProgrammingExerciseLifecycleModule } from 'app/exercises/programming/shared/lifecycle/programming-exercise-lifecycle.module';
import { AssessmentInstructionsModule } from 'app/assessment/assessment-instructions/assessment-instructions.module';
@@ -25,7 +24,6 @@ import { NoDataComponent } from 'app/shared/no-data-component';
SubmissionResultStatusModule,
ArtemisProgrammingExerciseStatusModule,
ArtemisProgrammingExerciseActionsModule,
- GitDiffReportModule,
ArtemisProgrammingExerciseInstructionsEditorModule,
ArtemisProgrammingExerciseLifecycleModule,
AssessmentInstructionsModule,
diff --git a/src/main/webapp/app/exam/manage/exam-management.module.ts b/src/main/webapp/app/exam/manage/exam-management.module.ts
index a932fdd227b9..b9a9829ab1fa 100644
--- a/src/main/webapp/app/exam/manage/exam-management.module.ts
+++ b/src/main/webapp/app/exam/manage/exam-management.module.ts
@@ -67,11 +67,11 @@ import { ArtemisExamNavigationBarModule } from 'app/exam/participate/exam-naviga
import { ArtemisExamSubmissionComponentsModule } from 'app/exam/participate/exercises/exam-submission-components.module';
import { MatSliderModule } from '@angular/material/slider';
import { ProgrammingExerciseExamDiffComponent } from './student-exams/student-exam-timeline/programming-exam-diff/programming-exercise-exam-diff.component';
-import { GitDiffReportModule } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report.module';
import { ArtemisProgrammingExerciseModule } from 'app/exercises/programming/shared/programming-exercise.module';
import { DetailModule } from 'app/detail-overview-list/detail.module';
import { ArtemisDurationFromSecondsPipe } from 'app/shared/pipes/artemis-duration-from-seconds.pipe';
import { NoDataComponent } from 'app/shared/no-data-component';
+import { GitDiffLineStatComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component';
const ENTITY_STATES = [...examManagementState];
@@ -110,10 +110,10 @@ const ENTITY_STATES = [...examManagementState];
ArtemisExamNavigationBarModule,
ArtemisExamSubmissionComponentsModule,
MatSliderModule,
- GitDiffReportModule,
ArtemisProgrammingExerciseModule,
DetailModule,
NoDataComponent,
+ GitDiffLineStatComponent,
],
declarations: [
ExamManagementComponent,
diff --git a/src/main/webapp/app/exam/manage/student-exams/student-exam-timeline/programming-exam-diff/programming-exercise-exam-diff.component.ts b/src/main/webapp/app/exam/manage/student-exams/student-exam-timeline/programming-exam-diff/programming-exercise-exam-diff.component.ts
index 7eafe4900d96..81a3f247a809 100644
--- a/src/main/webapp/app/exam/manage/student-exams/student-exam-timeline/programming-exam-diff/programming-exercise-exam-diff.component.ts
+++ b/src/main/webapp/app/exam/manage/student-exams/student-exam-timeline/programming-exam-diff/programming-exercise-exam-diff.component.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
+import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output, signal } from '@angular/core';
import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model';
import { ProgrammingSubmission } from 'app/entities/programming/programming-submission.model';
import { FeatureToggle } from 'app/shared/feature-toggle/feature-toggle.service';
@@ -123,10 +123,10 @@ export class ProgrammingExerciseExamDiffComponent extends ExamPageComponent impl
* Shows the git-diff in a modal.
*/
showGitDiff(): void {
- const modalRef = this.modalService.open(GitDiffReportModalComponent, { size: 'xl' });
- modalRef.componentInstance.report = this.exercise.gitDiffReport;
- modalRef.componentInstance.diffForTemplateAndSolution = false;
- modalRef.componentInstance.cachedRepositoryFiles = this.cachedRepositoryFiles;
+ const modalRef = this.modalService.open(GitDiffReportModalComponent, { windowClass: GitDiffReportModalComponent.WINDOW_CLASS });
+ modalRef.componentInstance.report = signal(this.exercise.gitDiffReport);
+ modalRef.componentInstance.diffForTemplateAndSolution = signal(false);
+ modalRef.componentInstance.cachedRepositoryFiles = signal(this.cachedRepositoryFiles);
this.cachedRepositoryFilesService.getCachedRepositoryFilesObservable().subscribe((cachedRepositoryFiles) => {
this.cachedRepositoryFiles = cachedRepositoryFiles;
});
diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.module.ts b/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.module.ts
index 05be60e77689..a9d7088b56c9 100644
--- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.module.ts
+++ b/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.module.ts
@@ -9,13 +9,13 @@ import { CoverageGenerationStepComponent } from '../steps/coverage-generation-st
import { SolutionEntryGenerationStepComponent } from '../steps/solution-entry-generation-step/solution-entry-generation-step.component';
import { CodeHintGenerationStepComponent } from '../steps/code-hint-generation-step/code-hint-generation-step.component';
import { TestwiseCoverageReportModule } from 'app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.module';
-import { GitDiffReportModule } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report.module';
import { CodeHintGenerationOverviewComponent } from 'app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.component';
import { RouterModule } from '@angular/router';
import { ArtemisMarkdownModule } from 'app/shared/markdown.module';
import { MatExpansionModule } from '@angular/material/expansion';
import { CodeHintGenerationStatusStepComponent } from 'app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status-step.component';
import { ManualSolutionEntryCreationModalComponent } from '../manual-solution-entry-creation-modal/manual-solution-entry-creation-modal.component';
+import { GitDiffReportComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report.component';
@NgModule({
imports: [
@@ -24,13 +24,9 @@ import { ManualSolutionEntryCreationModalComponent } from '../manual-solution-en
ArtemisExerciseHintSharedModule,
ArtemisSharedComponentModule,
TestwiseCoverageReportModule,
- GitDiffReportModule,
- GitDiffReportModule,
- TestwiseCoverageReportModule,
- ArtemisSharedComponentModule,
ArtemisMarkdownModule,
- ArtemisExerciseHintSharedModule,
MatExpansionModule,
+ GitDiffReportComponent,
],
declarations: [
CodeHintGenerationStatusComponent,
diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component.html b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component.html
index 80802af1dbd1..8b470a3e9fd5 100644
--- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component.html
+++ b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component.html
@@ -1,13 +1,13 @@
- {{ title }}
- @if (fileStatus !== FileStatus.UNCHANGED) {
+ {{ title() }}
+ @if (fileStatus() !== FileStatus.UNCHANGED) {
}
diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component.ts b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component.ts
index 3e7706581b88..74b0f35dfab0 100644
--- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component.ts
+++ b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component.ts
@@ -1,4 +1,6 @@
-import { Component, Input, OnInit } from '@angular/core';
+import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
+import { TranslateDirective } from 'app/shared/language/translate.directive';
+import { CommonModule } from '@angular/common';
enum FileStatus {
CREATED = 'created',
@@ -10,35 +12,33 @@ enum FileStatus {
selector: 'jhi-git-diff-file-panel-title',
templateUrl: './git-diff-file-panel-title.component.html',
styleUrls: ['./git-diff-file-panel-title.component.scss'],
+ standalone: true,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [TranslateDirective, CommonModule],
})
-export class GitDiffFilePanelTitleComponent implements OnInit {
- @Input()
- previousFilePath?: string;
+export class GitDiffFilePanelTitleComponent {
+ readonly originalFilePath = input
();
+ readonly modifiedFilePath = input();
- @Input()
- filePath?: string;
+ readonly titleAndFileStatus = computed(() => this.getTitleAndFileStatus());
+ readonly title = computed(() => this.titleAndFileStatus().title);
+ readonly fileStatus = computed(() => this.titleAndFileStatus().fileStatus);
- title?: string;
- fileStatus: FileStatus = FileStatus.UNCHANGED;
-
- // Expose to template
protected readonly FileStatus = FileStatus;
- ngOnInit(): void {
- if (this.filePath && this.previousFilePath) {
- if (this.filePath !== this.previousFilePath) {
- this.title = `${this.previousFilePath} → ${this.filePath}`;
- this.fileStatus = FileStatus.RENAMED;
+ private getTitleAndFileStatus(): { title?: string; fileStatus: FileStatus } {
+ const originalFilePath = this.originalFilePath();
+ const modifiedFilePath = this.modifiedFilePath();
+ if (modifiedFilePath && originalFilePath) {
+ if (modifiedFilePath !== originalFilePath) {
+ return { title: `${originalFilePath} → ${modifiedFilePath}`, fileStatus: FileStatus.RENAMED };
} else {
- this.title = this.filePath;
- this.fileStatus = FileStatus.UNCHANGED;
+ return { title: modifiedFilePath, fileStatus: FileStatus.UNCHANGED };
}
- } else if (this.filePath) {
- this.title = this.filePath;
- this.fileStatus = FileStatus.CREATED;
+ } else if (modifiedFilePath) {
+ return { title: modifiedFilePath, fileStatus: FileStatus.CREATED };
} else {
- this.title = this.previousFilePath;
- this.fileStatus = FileStatus.DELETED;
+ return { title: originalFilePath, fileStatus: FileStatus.DELETED };
}
}
}
diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component.html b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component.html
index 7b8fafd1178e..bb9245843140 100644
--- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component.html
+++ b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component.html
@@ -1,13 +1,13 @@
-
+
diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component.ts b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component.ts
index cd36cfc5b5f5..9e01edae26b9 100644
--- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component.ts
+++ b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component.ts
@@ -1,58 +1,66 @@
-import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
+import { ChangeDetectionStrategy, Component, ViewEncapsulation, computed, input, output } from '@angular/core';
import { ProgrammingExerciseGitDiffEntry } from 'app/entities/hestia/programming-exercise-git-diff-entry.model';
import { faAngleDown, faAngleUp } from '@fortawesome/free-solid-svg-icons';
+import { GitDiffFilePanelTitleComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component';
+import { GitDiffLineStatComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component';
+import { GitDiffFileComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file.component';
+import { ArtemisSharedModule } from 'app/shared/shared.module';
@Component({
selector: 'jhi-git-diff-file-panel',
templateUrl: './git-diff-file-panel.component.html',
styleUrls: ['./git-diff-file-panel.component.scss'],
encapsulation: ViewEncapsulation.None,
+ standalone: true,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [GitDiffFilePanelTitleComponent, GitDiffLineStatComponent, GitDiffFileComponent, ArtemisSharedModule],
})
-export class GitDiffFilePanelComponent implements OnInit {
- @Input() diffEntries: ProgrammingExerciseGitDiffEntry[];
+export class GitDiffFilePanelComponent {
+ protected readonly faAngleUp = faAngleUp;
+ protected readonly faAngleDown = faAngleDown;
- @Input() templateFileContent: string | undefined;
+ readonly diffEntries = input.required
();
+ readonly originalFileContent = input();
+ readonly modifiedFileContent = input();
+ readonly diffForTemplateAndSolution = input(true);
+ readonly allowSplitView = input(true);
+ readonly onDiffReady = output();
- @Input() solutionFileContent: string | undefined;
-
- @Input() diffForTemplateAndSolution = true;
-
- @Input() allowSplitView = true;
-
- @Output() onDiffReady = new EventEmitter();
-
- previousFilePath: string | undefined;
- filePath: string | undefined;
-
- addedLineCount: number;
- removedLineCount: number;
-
- faAngleUp = faAngleUp;
- faAngleDown = faAngleDown;
-
- ngOnInit(): void {
- this.filePath = this.diffEntries
- .map((entry) => entry.filePath)
+ readonly originalFilePath = computed(() =>
+ this.diffEntries()
+ .map((entry) => entry.previousFilePath)
.filter((filePath) => filePath)
- .first();
+ .first(),
+ );
- this.previousFilePath = this.diffEntries
- .map((entry) => entry.previousFilePath)
+ readonly modifiedFilePath = computed(() =>
+ this.diffEntries()
+ .map((entry) => entry.filePath)
.filter((filePath) => filePath)
- .first();
+ .first(),
+ );
- this.addedLineCount = this.diffEntries
- .filter((entry) => entry && entry.filePath && entry.startLine && entry.lineCount)
- .flatMap((entry) => {
- return this.solutionFileContent?.split('\n').slice(entry.startLine! - 1, entry.startLine! + entry.lineCount! - 1);
- })
- .filter((line) => line && line.trim().length !== 0).length;
+ readonly addedLineCount = computed(
+ () =>
+ this.diffEntries()
+ .filter((entry) => entry && entry.filePath && entry.startLine && entry.lineCount)
+ .flatMap((entry) => {
+ return this.modifiedFileContent()
+ ?.split('\n')
+ .slice(entry.startLine! - 1, entry.startLine! + entry.lineCount! - 1);
+ })
+ .filter((line) => line && line.trim().length !== 0).length,
+ );
- this.removedLineCount = this.diffEntries
- .filter((entry) => entry && entry.previousFilePath && entry.previousStartLine && entry.previousLineCount)
- .flatMap((entry) => {
- return this.templateFileContent?.split('\n').slice(entry.previousStartLine! - 1, entry.previousStartLine! + entry.previousLineCount! - 1);
- })
- .filter((line) => line && line.trim().length !== 0).length;
- }
+ readonly removedLineCount = computed(
+ () =>
+ this.diffEntries()
+ .filter((entry) => entry && entry.previousFilePath && entry.previousStartLine && entry.previousLineCount)
+ .flatMap((entry) => {
+ return this.originalFileContent()
+ ?.split('\n')
+ .slice(entry.previousStartLine! - 1, entry.previousStartLine! + entry.previousLineCount! - 1);
+ })
+ .filter((line) => line && line.trim().length !== 0).length,
+ );
}
diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file.component.html b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file.component.html
index e06148d20072..2f9ca3570472 100644
--- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file.component.html
+++ b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file.component.html
@@ -1,9 +1,9 @@
- @if (fileUnchanged) {
+ @if (fileUnchanged()) {
}
-
+
diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file.component.ts b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file.component.ts
index 8c5c283c3c68..45456a115513 100644
--- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file.component.ts
+++ b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file.component.ts
@@ -1,56 +1,31 @@
-import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core';
+import { ChangeDetectionStrategy, Component, ViewEncapsulation, computed, effect, input, output, viewChild } from '@angular/core';
import { ProgrammingExerciseGitDiffEntry } from 'app/entities/hestia/programming-exercise-git-diff-entry.model';
import { MonacoDiffEditorComponent } from 'app/shared/monaco-editor/monaco-diff-editor.component';
+import { TranslateDirective } from 'app/shared/language/translate.directive';
@Component({
selector: 'jhi-git-diff-file',
templateUrl: './git-diff-file.component.html',
encapsulation: ViewEncapsulation.None,
+ standalone: true,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [MonacoDiffEditorComponent, TranslateDirective],
})
-export class GitDiffFileComponent implements OnInit {
- @ViewChild(MonacoDiffEditorComponent, { static: true })
- monacoDiffEditor: MonacoDiffEditorComponent;
-
- @Input()
- diffForTemplateAndSolution: boolean = false;
-
- @Input()
- diffEntries: ProgrammingExerciseGitDiffEntry[];
-
- @Input()
- originalFileContent?: string;
-
- @Input()
- modifiedFileContent?: string;
-
- @Input()
- allowSplitView = true;
-
- @Output()
- onDiffReady = new EventEmitter();
-
- originalFilePath?: string;
- modifiedFilePath?: string;
- fileUnchanged = false;
-
- ngOnInit(): void {
- this.determineFilePaths();
- this.monacoDiffEditor.setFileContents(this.originalFileContent, this.originalFilePath, this.modifiedFileContent, this.modifiedFilePath);
- this.fileUnchanged = this.originalFileContent === this.modifiedFileContent;
- }
-
- /**
- * Determines the previous and current file path of the current file
- */
- private determineFilePaths() {
- this.modifiedFilePath = this.diffEntries
- .map((entry) => entry.filePath)
- .filter((filePath) => filePath)
- .first();
-
- this.originalFilePath = this.diffEntries
- .map((entry) => entry.previousFilePath)
- .filter((filePath) => filePath)
- .first();
+export class GitDiffFileComponent {
+ readonly monacoDiffEditor = viewChild.required(MonacoDiffEditorComponent);
+ readonly diffForTemplateAndSolution = input(false);
+ readonly diffEntries = input.required();
+ readonly originalFileContent = input();
+ readonly originalFilePath = input();
+ readonly modifiedFileContent = input();
+ readonly modifiedFilePath = input();
+ readonly allowSplitView = input(true);
+ readonly onDiffReady = output();
+ readonly fileUnchanged = computed(() => this.originalFileContent() === this.modifiedFileContent());
+
+ constructor() {
+ effect(() => {
+ this.monacoDiffEditor().setFileContents(this.originalFileContent(), this.originalFilePath(), this.modifiedFileContent(), this.modifiedFilePath());
+ });
}
}
diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component.html b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component.html
index d23e649b7439..654c3a70afed 100644
--- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component.html
+++ b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component.html
@@ -1,11 +1,11 @@
- +{{ addedLineCount }}
- -{{ removedLineCount }}
+ +{{ addedLineCount() }}
+ -{{ removedLineCount() }}
- @for (i of [].constructor(addedSquareCount); track $index) {
+ @for (i of addedSquareArray(); track $index) {
}
- @for (i of [].constructor(removedSquareCount); track $index) {
+ @for (i of removedSquareArray(); track $index) {
}
diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component.ts b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component.ts
index 8f25b7794bfc..b82ab8d02c02 100644
--- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component.ts
+++ b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component.ts
@@ -1,34 +1,34 @@
-import { Component, Input, OnInit } from '@angular/core';
+import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
@Component({
selector: 'jhi-git-diff-line-stat',
templateUrl: './git-diff-line-stat.component.html',
styleUrls: ['./git-diff-line-stat.component.scss'],
+ standalone: true,
+ changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class GitDiffLineStatComponent implements OnInit {
- @Input()
- addedLineCount: number = 0;
- @Input()
- removedLineCount: number = 0;
- addedSquareCount: number;
- removedSquareCount: number;
+export class GitDiffLineStatComponent {
+ readonly addedLineCount = input(0);
+ readonly removedLineCount = input(0);
+ readonly squareCounts = computed(() => this.getSquareCounts());
+ readonly addedSquareArray = computed(() => Array.from({ length: this.squareCounts().addedSquareCount }));
+ readonly removedSquareArray = computed(() => Array.from({ length: this.squareCounts().removedSquareCount }));
- ngOnInit(): void {
- if (!this.addedLineCount && !this.removedLineCount) {
- this.addedSquareCount = 1;
- this.removedSquareCount = 1;
- } else if (this.addedLineCount === 0) {
- this.addedSquareCount = 0;
- this.removedSquareCount = 5;
- } else if (this.removedLineCount === 0) {
- this.addedSquareCount = 5;
- this.removedSquareCount = 0;
+ private getSquareCounts(): { addedSquareCount: number; removedSquareCount: number } {
+ const addedLineCount = this.addedLineCount();
+ const removedLineCount = this.removedLineCount();
+ if (!addedLineCount && !removedLineCount) {
+ return { addedSquareCount: 1, removedSquareCount: 1 };
+ } else if (addedLineCount === 0) {
+ return { addedSquareCount: 0, removedSquareCount: 5 };
+ } else if (removedLineCount === 0) {
+ return { addedSquareCount: 5, removedSquareCount: 0 };
} else {
- const totalLineCount = this.addedLineCount + this.removedLineCount;
+ const totalLineCount = addedLineCount + removedLineCount;
// Calculates the amount of green rectangles to show between 1 and 4
// This is the rounded percentage of added lines divided by total lines
- this.addedSquareCount = Math.round(Math.max(1, Math.min(4, (this.addedLineCount / totalLineCount) * 5)));
- this.removedSquareCount = 5 - this.addedSquareCount;
+ const addedSquareCount = Math.round(Math.max(1, Math.min(4, (addedLineCount / totalLineCount) * 5)));
+ return { addedSquareCount, removedSquareCount: 5 - addedSquareCount };
}
}
}
diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component.html b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component.html
index 198b27469831..6b95cf90f178 100644
--- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component.html
+++ b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component.html
@@ -2,22 +2,22 @@
- @if (leftCommitFileContentByPath && rightCommitFileContentByPath) {
+ @if (leftCommitFileContentByPath() && rightCommitFileContentByPath()) {
} @else {
- @if (!errorWhileFetchingRepos) {
+ @if (!errorWhileFetchingRepos()) {
Loading...
@@ -25,16 +25,7 @@
}
}
-
- @if (!errorWhileFetchingRepos) {
-
-
- Loading...
-
-
- }
-
- @if (errorWhileFetchingRepos) {
+ @if (errorWhileFetchingRepos()) {
diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component.ts b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component.ts
index 107acb55c1de..7684573f821f 100644
--- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component.ts
+++ b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component.ts
@@ -1,144 +1,148 @@
-import { Component, Input, OnDestroy, OnInit } from '@angular/core';
+import { ChangeDetectionStrategy, Component, effect, inject, input, signal, untracked } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model';
import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service';
import { ProgrammingExerciseParticipationService } from 'app/exercises/programming/manage/services/programming-exercise-participation.service';
import { CachedRepositoryFilesService } from 'app/exercises/programming/manage/services/cached-repository-files.service';
-import { Subscription } from 'rxjs';
+import { firstValueFrom } from 'rxjs';
+import { GitDiffReportComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report.component';
+import { TranslateDirective } from 'app/shared/language/translate.directive';
@Component({
selector: 'jhi-git-diff-report-modal',
templateUrl: './git-diff-report-modal.component.html',
+ standalone: true,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [GitDiffReportComponent, TranslateDirective],
})
-export class GitDiffReportModalComponent implements OnInit, OnDestroy {
- @Input() report: ProgrammingExerciseGitDiffReport;
- @Input() diffForTemplateAndSolution = true;
- @Input() cachedRepositoryFiles: Map
> = new Map>();
-
- errorWhileFetchingRepos = false;
- leftCommitFileContentByPath: Map;
- rightCommitFileContentByPath: Map;
-
- private templateRepoFilesSubscription: Subscription;
- private solutionRepoFilesSubscription: Subscription;
- private participationRepoFilesAtLeftCommitSubscription: Subscription;
- private participationRepoFilesAtRightCommitSubscription: Subscription;
-
- constructor(
- protected activeModal: NgbActiveModal,
- private programmingExerciseService: ProgrammingExerciseService,
- private programmingExerciseParticipationService: ProgrammingExerciseParticipationService,
- private cachedRepositoryFilesService: CachedRepositoryFilesService,
- ) {}
-
- ngOnInit(): void {
- if (this.diffForTemplateAndSolution) {
- this.loadFilesForTemplateAndSolution();
- } else {
- this.loadRepositoryFilesForParticipationsFromCacheIfAvailable();
- }
- }
-
- ngOnDestroy(): void {
- this.templateRepoFilesSubscription?.unsubscribe();
- this.solutionRepoFilesSubscription?.unsubscribe();
- this.participationRepoFilesAtLeftCommitSubscription?.unsubscribe();
- this.participationRepoFilesAtRightCommitSubscription?.unsubscribe();
+export class GitDiffReportModalComponent {
+ static readonly WINDOW_CLASS = 'diff-view-modal';
+
+ private readonly activeModal = inject(NgbActiveModal);
+ private readonly programmingExerciseService = inject(ProgrammingExerciseService);
+ private readonly programmingExerciseParticipationService = inject(ProgrammingExerciseParticipationService);
+ private readonly cachedRepositoryFilesService = inject(CachedRepositoryFilesService);
+
+ readonly report = input.required();
+ readonly diffForTemplateAndSolution = input(true);
+ readonly cachedRepositoryFiles = input
-
- @for (diffInformation of diffInformationForPaths; track diffInformation) {
+
+ @for (diffInformation of diffInformationForPaths(); track diffInformation) {
}
- @if (nothingToDisplay) {
+ @if (nothingToDisplay()) {
- } @else if (!allDiffsReady) {
+ } @else if (!allDiffsReady()) {
diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.component.ts b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.component.ts
index 87f30aade34a..cc916e5daf93 100644
--- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.component.ts
+++ b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.component.ts
@@ -1,8 +1,12 @@
-import { Component, Input, OnInit } from '@angular/core';
+import { ChangeDetectionStrategy, Component, computed, effect, input, signal, untracked } from '@angular/core';
import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model';
import { ProgrammingExerciseGitDiffEntry } from 'app/entities/hestia/programming-exercise-git-diff-entry.model';
import { faSpinner, faTableColumns } from '@fortawesome/free-solid-svg-icons';
-import { ButtonSize, ButtonType } from 'app/shared/components/button.component';
+import { ButtonSize, ButtonType, TooltipPlacement } from 'app/shared/components/button.component';
+import { GitDiffLineStatComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component';
+import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module';
+import { ArtemisSharedModule } from 'app/shared/shared.module';
+import { GitDiffFilePanelComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component';
interface DiffInformation {
path: string;
@@ -15,49 +19,27 @@ interface DiffInformation {
@Component({
selector: 'jhi-git-diff-report',
templateUrl: './git-diff-report.component.html',
+ standalone: true,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [GitDiffLineStatComponent, ArtemisSharedModule, ArtemisSharedComponentModule, GitDiffFilePanelComponent],
})
-export class GitDiffReportComponent implements OnInit {
- @Input() report: ProgrammingExerciseGitDiffReport;
-
- @Input() templateFileContentByPath: Map
;
-
- @Input() solutionFileContentByPath: Map;
-
- @Input() filePaths: string[];
-
- @Input() diffForTemplateAndSolution = true;
-
- @Input() diffForTemplateAndEmptyRepository = false;
-
- @Input() isRepositoryView = false;
-
- leftCommit: string | undefined;
-
- rightCommit: string | undefined;
-
- entries: ProgrammingExerciseGitDiffEntry[];
- entriesByPath: Map;
- addedLineCount: number;
- removedLineCount: number;
- diffInformationForPaths: DiffInformation[] = [];
- allDiffsReady = false;
- nothingToDisplay = false;
- allowSplitView = true;
- renamedFilePaths: { [before: string]: string | undefined } = {};
-
- faSpinner = faSpinner;
- faTableColumns = faTableColumns;
-
- // Expose to template
+export class GitDiffReportComponent {
+ protected readonly faSpinner = faSpinner;
+ protected readonly faTableColumns = faTableColumns;
protected readonly ButtonSize = ButtonSize;
protected readonly ButtonType = ButtonType;
-
- constructor() {}
-
- ngOnInit(): void {
- // Sort the diff entries by file path and start lines
- this.entries =
- this.report.entries?.sort((a, b) => {
+ protected readonly TooltipPlacement = TooltipPlacement;
+
+ readonly report = input.required();
+ readonly templateFileContentByPath = input.required>();
+ readonly solutionFileContentByPath = input.required>();
+ readonly diffForTemplateAndSolution = input(true);
+ readonly diffForTemplateAndEmptyRepository = input(false);
+ readonly isRepositoryView = input(false);
+
+ readonly sortedEntries = computed(() => {
+ return (
+ this.report().entries?.sort((a, b) => {
const filePathA = a.filePath ?? a.previousFilePath ?? '';
const filePathB = b.filePath ?? b.previousFilePath ?? '';
if (filePathA < filePathB) {
@@ -67,65 +49,91 @@ export class GitDiffReportComponent implements OnInit {
return 1;
}
return (a.startLine ?? a.previousStartLine ?? 0) - (b.startLine ?? b.previousStartLine ?? 0);
- }) ?? [];
+ }) ?? []
+ );
+ });
- this.addedLineCount = this.entries
+ readonly addedLineCount = computed(() => {
+ return this.sortedEntries()
.flatMap((entry) => {
if (entry && entry.filePath && entry.startLine && entry.lineCount) {
- return this.solutionFileContentByPath
+ return this.solutionFileContentByPath()
.get(entry.filePath)
?.split('\n')
.slice(entry.startLine - 1, entry.startLine + entry.lineCount - 1);
}
})
.filter((line) => line && line.trim().length !== 0).length;
+ });
- this.removedLineCount = this.entries
+ readonly removedLineCount = computed(() => {
+ return this.sortedEntries()
.flatMap((entry) => {
if (entry && entry.previousFilePath && entry.previousStartLine && entry.previousLineCount) {
- return this.templateFileContentByPath
+ return this.templateFileContentByPath()
.get(entry.previousFilePath!)
?.split('\n')
.slice(entry.previousStartLine - 1, entry.previousStartLine + entry.previousLineCount - 1);
}
})
.filter((line) => line && line.trim().length !== 0).length;
+ });
+
+ readonly filePaths = computed(() => {
+ return [...new Set([...this.templateFileContentByPath().keys(), ...this.solutionFileContentByPath().keys()])].sort();
+ });
- // Create a set of all file paths
- this.filePaths = [...new Set([...this.templateFileContentByPath.keys(), ...this.solutionFileContentByPath.keys()])].sort();
- // Track renamed files
- this.entries.forEach((entry) => {
+ readonly renamedFilePaths = computed(() => {
+ const renamedFilePaths: { [before: string]: string | undefined } = {};
+ this.sortedEntries().forEach((entry) => {
// Accounts only for files that have existed in the original and the modified version, but under different names
if (entry.filePath && entry.previousFilePath && entry.filePath !== entry.previousFilePath) {
- this.renamedFilePaths[entry.filePath] = entry.previousFilePath;
+ renamedFilePaths[entry.filePath] = entry.previousFilePath;
}
});
- // Group the diff entries by file path
- this.entriesByPath = new Map();
- [...this.templateFileContentByPath.keys()].forEach((filePath) => {
- this.entriesByPath.set(
+ return renamedFilePaths;
+ });
+
+ readonly entriesByPath = computed(() => {
+ const entriesByPath = new Map();
+ [...this.templateFileContentByPath().keys()].forEach((filePath) => {
+ entriesByPath.set(
filePath,
- this.entries.filter((entry) => entry.previousFilePath === filePath && !entry.filePath),
+ this.sortedEntries().filter((entry) => entry.previousFilePath === filePath && !entry.filePath),
);
});
- [...this.solutionFileContentByPath.keys()].forEach((filePath) => {
- this.entriesByPath.set(
+ [...this.solutionFileContentByPath().keys()].forEach((filePath) => {
+ entriesByPath.set(
filePath,
- this.entries.filter((entry) => entry.filePath === filePath),
+ this.sortedEntries().filter((entry) => entry.filePath === filePath),
);
});
- this.leftCommit = this.report.leftCommitHash?.substring(0, 10);
- this.rightCommit = this.report.rightCommitHash?.substring(0, 10);
- this.diffInformationForPaths = this.filePaths
- .filter((path) => this.entriesByPath.get(path)?.length)
- .map((path) => {
- // entries is not undefined due to the filter
- const entries = this.entriesByPath.get(path)!;
- const templateFileContent = this.templateFileContentByPath.get(this.renamedFilePaths[path] ?? path);
- const solutionFileContent = this.solutionFileContentByPath.get(path);
- return { path, entries, templateFileContent, solutionFileContent, diffReady: false };
+ return entriesByPath;
+ });
+
+ readonly leftCommit = computed(() => this.report().leftCommitHash?.substring(0, 10));
+ readonly rightCommit = computed(() => this.report().rightCommitHash?.substring(0, 10));
+ readonly diffInformationForPaths = signal([]);
+ readonly nothingToDisplay = computed(() => this.diffInformationForPaths().length === 0);
+ readonly allDiffsReady = computed(() => Object.values(this.diffInformationForPaths()).every((info) => info.diffReady));
+ readonly allowSplitView = signal(true);
+
+ constructor() {
+ effect(() => {
+ untracked(() => {
+ this.diffInformationForPaths.set(
+ this.filePaths()
+ .filter((path) => this.entriesByPath().get(path)?.length)
+ .map((path) => {
+ // entries is not undefined due to the filter above
+ const entries = this.entriesByPath().get(path)!;
+ const templateFileContent = this.templateFileContentByPath().get(this.renamedFilePaths()[path] ?? path);
+ const solutionFileContent = this.solutionFileContentByPath().get(path);
+ return { path, entries, templateFileContent, solutionFileContent, diffReady: false };
+ }),
+ );
});
- this.nothingToDisplay = this.diffInformationForPaths.length === 0;
+ });
}
/**
@@ -135,10 +143,11 @@ export class GitDiffReportComponent implements OnInit {
* @param ready Whether the diff is ready to be displayed or not.
*/
onDiffReady(path: string, ready: boolean) {
- const index = this.diffInformationForPaths.findIndex((info) => info.path === path);
+ const diffInformation = [...this.diffInformationForPaths()];
+ const index = diffInformation.findIndex((info) => info.path === path);
if (index !== -1) {
- this.diffInformationForPaths[index].diffReady = ready;
- this.allDiffsReady = Object.values(this.diffInformationForPaths).every((info) => info.diffReady);
+ diffInformation[index].diffReady = ready;
+ this.diffInformationForPaths.set(diffInformation);
} else {
console.error(`Received diff ready event for unknown path: ${path}`);
}
diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.module.ts b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.module.ts
deleted file mode 100644
index 17b5682de4cb..000000000000
--- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.module.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { NgModule } from '@angular/core';
-import { ArtemisSharedModule } from 'app/shared/shared.module';
-import { GitDiffLineStatComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component';
-import { GitDiffReportComponent } from './git-diff-report.component';
-import { GitDiffFileComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file.component';
-import { GitDiffReportModalComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component';
-import { GitDiffFilePanelComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component';
-import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap';
-import { GitDiffFilePanelTitleComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component';
-import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module';
-import { MonacoDiffEditorComponent } from 'app/shared/monaco-editor/monaco-diff-editor.component';
-
-@NgModule({
- imports: [ArtemisSharedModule, NgbAccordionModule, MonacoDiffEditorComponent, ArtemisSharedComponentModule],
- declarations: [GitDiffFilePanelComponent, GitDiffFilePanelTitleComponent, GitDiffReportComponent, GitDiffFileComponent, GitDiffReportModalComponent, GitDiffLineStatComponent],
- exports: [GitDiffReportComponent, GitDiffReportModalComponent, GitDiffLineStatComponent],
-})
-export class GitDiffReportModule {}
diff --git a/src/main/webapp/app/exercises/programming/manage/programming-exercise-management.module.ts b/src/main/webapp/app/exercises/programming/manage/programming-exercise-management.module.ts
index ce2056d24a44..7340599252c5 100644
--- a/src/main/webapp/app/exercises/programming/manage/programming-exercise-management.module.ts
+++ b/src/main/webapp/app/exercises/programming/manage/programming-exercise-management.module.ts
@@ -19,7 +19,6 @@ import { ArtemisPlagiarismModule } from 'app/exercises/shared/plagiarism/plagiar
import { ArtemisProgrammingExerciseLifecycleModule } from 'app/exercises/programming/shared/lifecycle/programming-exercise-lifecycle.module';
import { ProgrammingExerciseInstructorExerciseDownloadComponent } from '../shared/actions/programming-exercise-instructor-exercise-download.component';
import { SubmissionResultStatusModule } from 'app/overview/submission-result-status.module';
-import { GitDiffReportModule } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report.module';
import { ProgrammingExerciseExampleSolutionRepoDownloadComponent } from 'app/exercises/programming/shared/actions/programming-exercise-example-solution-repo-download.component';
import { TestwiseCoverageReportModule } from 'app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.module';
import { ArtemisCodeHintGenerationOverviewModule } from 'app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.module';
@@ -49,7 +48,6 @@ import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.co
OrionModule,
ArtemisProgrammingExerciseLifecycleModule,
SubmissionResultStatusModule,
- GitDiffReportModule,
TestwiseCoverageReportModule,
ArtemisCodeHintGenerationOverviewModule,
ArtemisCodeEditorModule,
diff --git a/src/main/webapp/app/exercises/programming/participate/programming-repository.module.ts b/src/main/webapp/app/exercises/programming/participate/programming-repository.module.ts
index c1b68191f38a..d90997bc2ccd 100644
--- a/src/main/webapp/app/exercises/programming/participate/programming-repository.module.ts
+++ b/src/main/webapp/app/exercises/programming/participate/programming-repository.module.ts
@@ -18,8 +18,8 @@ import { FormsModule } from '@angular/forms';
import { ArtemisProgrammingExerciseModule } from 'app/exercises/programming/shared/programming-exercise.module';
import { CommitHistoryComponent } from 'app/localvc/commit-history/commit-history.component';
import { CommitDetailsViewComponent } from 'app/localvc/commit-details-view/commit-details-view.component';
-import { GitDiffReportModule } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report.module';
import { ArtemisProgrammingExerciseActionsModule } from 'app/exercises/programming/shared/actions/programming-exercise-actions.module';
+import { GitDiffReportComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report.component';
@NgModule({
imports: [
@@ -39,8 +39,8 @@ import { ArtemisProgrammingExerciseActionsModule } from 'app/exercises/programmi
ArtemisHeaderExercisePageWithDetailsModule,
SubmissionResultStatusModule,
ArtemisProgrammingExerciseModule,
- GitDiffReportModule,
ArtemisProgrammingExerciseActionsModule,
+ GitDiffReportComponent,
],
declarations: [RepositoryViewComponent, CommitHistoryComponent, CommitDetailsViewComponent],
exports: [RepositoryViewComponent],
diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.scss b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.scss
index 85f7d08ca87a..34b221d82d90 100644
--- a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.scss
+++ b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.scss
@@ -1,3 +1,12 @@
+.diff-editor-container {
+ .monaco-diff-editor {
+ .monaco-editor {
+ // Disables the focus border around the editor.
+ outline: none;
+ }
+ }
+}
+
/*
* Hide the drag handles of the hidden lines, as this feature would result in unintuitive behavior.
*/
diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts
index 8cb86be9a233..bd234e45cdec 100644
--- a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts
+++ b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewEncapsulation, effect, inject, input, output } from '@angular/core';
+import { ChangeDetectionStrategy, Component, ElementRef, OnDestroy, Renderer2, ViewEncapsulation, effect, inject, input, output } from '@angular/core';
import * as monaco from 'monaco-editor';
import { Disposable } from 'app/shared/monaco-editor/model/actions/monaco-editor.util';
@@ -13,7 +13,7 @@ export type MonacoEditorDiffText = { original: string; modified: string };
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
-export class MonacoDiffEditorComponent implements OnInit, OnDestroy {
+export class MonacoDiffEditorComponent implements OnDestroy {
private _editor: monaco.editor.IStandaloneDiffEditor;
monacoDiffEditorContainerElement: HTMLElement;
@@ -24,7 +24,6 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy {
* Subscriptions and listeners that need to be disposed of when this component is destroyed.
*/
listeners: Disposable[] = [];
- resizeObserver?: ResizeObserver;
/*
* Injected services and elements.
@@ -42,6 +41,7 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy {
this.monacoDiffEditorContainerElement = this.renderer.createElement('div');
this._editor = this.monacoEditorService.createStandaloneDiffEditor(this.monacoDiffEditorContainerElement);
this.renderer.appendChild(this.elementRef.nativeElement, this.monacoDiffEditorContainerElement);
+ this.renderer.addClass(this.monacoDiffEditorContainerElement, 'diff-editor-container');
this.setupDiffListener();
this.setupContentHeightListeners();
@@ -52,15 +52,7 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy {
});
}
- ngOnInit(): void {
- this.resizeObserver = new ResizeObserver(() => {
- this.layout();
- });
- this.resizeObserver.observe(this.monacoDiffEditorContainerElement);
- }
-
ngOnDestroy(): void {
- this.resizeObserver?.disconnect();
this.listeners.forEach((listener) => {
listener.dispose();
});
@@ -73,7 +65,7 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy {
*/
setupDiffListener(): void {
const diffListener = this._editor.onDidUpdateDiff(() => {
- this.adjustHeightAndLayout(this.getMaximumContentHeight());
+ this.adjustContainerHeight(this.getMaximumContentHeight());
this.onReadyForDisplayChange.emit(true);
});
@@ -91,13 +83,13 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy {
const contentSizeListener = editor.onDidContentSizeChange((e: monaco.editor.IContentSizeChangedEvent) => {
if (e.contentHeightChanged) {
// Using the content height of the larger editor here ensures that neither of the editors break out of the container.
- this.adjustHeightAndLayout(this.getMaximumContentHeight());
+ this.adjustContainerHeight(this.getMaximumContentHeight());
}
});
// Called when the user reveals or collapses a hidden region.
const hiddenAreaListener = editor.onDidChangeHiddenAreas(() => {
- this.adjustHeightAndLayout(this.getContentHeightOfEditor(editor));
+ this.adjustContainerHeight(this.getContentHeightOfEditor(editor));
});
this.listeners.push(contentSizeListener, hiddenAreaListener);
@@ -105,21 +97,11 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy {
}
/**
- * Adjusts the height of the editor to fit the new content height.
+ * Adjusts the height of the editor's container to fit the new content height.
* @param newContentHeight The new content height of the editor.
*/
- adjustHeightAndLayout(newContentHeight: number) {
+ adjustContainerHeight(newContentHeight: number) {
this.monacoDiffEditorContainerElement.style.height = newContentHeight + 'px';
- this.layout();
- }
-
- /**
- * Adjusts this editor to fit its container.
- */
- layout(): void {
- const width = this.monacoDiffEditorContainerElement.clientWidth;
- const height = this.monacoDiffEditorContainerElement.clientHeight;
- this._editor.layout({ width, height });
}
/**
diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts
index 0d00d81300a3..b16cb7cf6b18 100644
--- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts
+++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts
@@ -61,6 +61,7 @@ export class MonacoEditorService {
*/
createStandaloneDiffEditor(domElement: HTMLElement): monaco.editor.IStandaloneDiffEditor {
return monaco.editor.createDiffEditor(domElement, {
+ automaticLayout: true,
glyphMargin: true,
minimap: { enabled: false },
readOnly: true,
diff --git a/src/test/javascript/spec/component/exam/manage/programming-exam-diff.component.spec.ts b/src/test/javascript/spec/component/exam/manage/programming-exam-diff.component.spec.ts
index cb10c66e1574..3a0d9d6e640c 100644
--- a/src/test/javascript/spec/component/exam/manage/programming-exam-diff.component.spec.ts
+++ b/src/test/javascript/spec/component/exam/manage/programming-exam-diff.component.spec.ts
@@ -83,7 +83,7 @@ describe('ProgrammingExerciseExamDiffComponent', () => {
const modalServiceSpy = jest.spyOn(modal, 'open');
component.exercise = { id: 1 } as ProgrammingExercise;
component.showGitDiff();
- expect(modalServiceSpy).toHaveBeenCalledExactlyOnceWith(GitDiffReportModalComponent, { size: 'xl' });
+ expect(modalServiceSpy).toHaveBeenCalledExactlyOnceWith(GitDiffReportModalComponent, { windowClass: GitDiffReportModalComponent.WINDOW_CLASS });
});
it('should use reports from cache if available', fakeAsync(() => {
diff --git a/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file-panel-title.component.spec.ts b/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file-panel-title.component.spec.ts
index 3dc633eb4b50..86380ebed0a7 100644
--- a/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file-panel-title.component.spec.ts
+++ b/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file-panel-title.component.spec.ts
@@ -22,34 +22,34 @@ describe('GitDiffFilePanelTitleComponent', () => {
it.each([
{
- filePath: 'some-unchanged-file.java',
- previousFilePath: 'some-unchanged-file.java',
+ modifiedFilePath: 'some-unchanged-file.java',
+ originalFilePath: 'some-unchanged-file.java',
status: 'unchanged',
title: 'some-unchanged-file.java',
},
{
- filePath: undefined,
- previousFilePath: 'some-deleted-file.java',
+ modifiedFilePath: undefined,
+ originalFilePath: 'some-deleted-file.java',
status: 'deleted',
title: 'some-deleted-file.java',
},
{
- filePath: 'some-created-file.java',
- previousFilePath: undefined,
+ modifiedFilePath: 'some-created-file.java',
+ originalFilePath: undefined,
status: 'created',
title: 'some-created-file.java',
},
{
- filePath: 'some-renamed-file.java',
- previousFilePath: 'some-file.java',
+ modifiedFilePath: 'some-renamed-file.java',
+ originalFilePath: 'some-file.java',
status: 'renamed',
title: 'some-file.java → some-renamed-file.java',
},
- ])('should correctly set title and status', ({ filePath, previousFilePath, status, title }) => {
- comp.previousFilePath = previousFilePath;
- comp.filePath = filePath;
+ ])('should correctly set title and status', ({ originalFilePath, modifiedFilePath, status, title }) => {
+ fixture.componentRef.setInput('originalFilePath', originalFilePath);
+ fixture.componentRef.setInput('modifiedFilePath', modifiedFilePath);
fixture.detectChanges();
- expect(comp.title).toBe(title);
- expect(comp.fileStatus).toBe(status);
+ expect(comp.title()).toBe(title);
+ expect(comp.fileStatus()).toBe(status);
});
});
diff --git a/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file-panel.component.spec.ts b/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file-panel.component.spec.ts
index d4938caea953..26627dba3e8f 100644
--- a/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file-panel.component.spec.ts
+++ b/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file-panel.component.spec.ts
@@ -1,29 +1,27 @@
import { ArtemisTestModule } from '../../../test.module';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
-import { MockComponent, MockDirective, MockPipe } from 'ng-mocks';
+import { MockComponent, MockDirective } from 'ng-mocks';
import { GitDiffLineStatComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component';
import { ProgrammingExerciseGitDiffEntry } from 'app/entities/hestia/programming-exercise-git-diff-entry.model';
import { GitDiffFilePanelComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component';
-import { DeleteButtonDirective } from 'app/shared/delete-dialog/delete-button.directive';
import { NgbAccordionBody, NgbAccordionButton, NgbAccordionCollapse, NgbAccordionDirective, NgbAccordionHeader, NgbAccordionItem } from '@ng-bootstrap/ng-bootstrap';
import { GitDiffFileComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file.component';
import { GitDiffFilePanelTitleComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component';
+import { MonacoDiffEditorComponent } from '../../../../../../main/webapp/app/shared/monaco-editor/monaco-diff-editor.component';
-describe('ProgrammingExerciseGitDiffFilePanel Component', () => {
+describe('GitDiffFilePanelComponent', () => {
let comp: GitDiffFilePanelComponent;
let fixture: ComponentFixture;
beforeEach(() => {
TestBed.configureTestingModule({
- imports: [ArtemisTestModule],
+ // TODO: We cannot mock GitDiffFileComponent because of https://github.com/help-me-mom/ng-mocks/issues/8634.
+ imports: [ArtemisTestModule, GitDiffFileComponent],
declarations: [
GitDiffFilePanelComponent,
- MockPipe(ArtemisTranslatePipe),
MockComponent(GitDiffFilePanelTitleComponent),
MockComponent(GitDiffLineStatComponent),
- MockComponent(GitDiffFileComponent),
- MockDirective(DeleteButtonDirective),
+ MockComponent(MonacoDiffEditorComponent),
MockDirective(NgbAccordionDirective),
MockDirective(NgbAccordionItem),
MockDirective(NgbAccordionHeader),
@@ -35,8 +33,8 @@ describe('ProgrammingExerciseGitDiffFilePanel Component', () => {
}).compileComponents();
fixture = TestBed.createComponent(GitDiffFilePanelComponent);
comp = fixture.componentInstance;
- comp.templateFileContent = 'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16';
- comp.solutionFileContent = 'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16';
+ fixture.componentRef.setInput('originalFileContent', 'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16');
+ fixture.componentRef.setInput('modifiedFileContent', 'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16');
});
afterEach(() => {
@@ -44,21 +42,21 @@ describe('ProgrammingExerciseGitDiffFilePanel Component', () => {
});
it('Should extract file path', () => {
- comp.diffEntries = [{ filePath: 'src/a.java', previousFilePath: 'src/b.java' }] as ProgrammingExerciseGitDiffEntry[];
- comp.ngOnInit();
- expect(comp.filePath).toBe('src/a.java');
- expect(comp.previousFilePath).toBe('src/b.java');
+ fixture.componentRef.setInput('diffEntries', [{ filePath: 'src/a.java', previousFilePath: 'src/b.java' }] as ProgrammingExerciseGitDiffEntry[]);
+ fixture.detectChanges();
+ expect(comp.modifiedFilePath()).toBe('src/a.java');
+ expect(comp.originalFilePath()).toBe('src/b.java');
});
it('Should set added/removed lines to 1-0', () => {
- comp.diffEntries = [{ filePath: 'src/a.java', startLine: 1, lineCount: 1 }] as ProgrammingExerciseGitDiffEntry[];
- comp.ngOnInit();
- expect(comp.addedLineCount).toBe(1);
- expect(comp.removedLineCount).toBe(0);
+ fixture.componentRef.setInput('diffEntries', [{ filePath: 'src/a.java', startLine: 1, lineCount: 1 }] as ProgrammingExerciseGitDiffEntry[]);
+ fixture.detectChanges();
+ expect(comp.addedLineCount()).toBe(1);
+ expect(comp.removedLineCount()).toBe(0);
});
it('Should set added/removed lines to 4-1', () => {
- comp.diffEntries = [
+ fixture.componentRef.setInput('diffEntries', [
{
filePath: 'src/a.java',
previousFilePath: 'src/a.java',
@@ -67,14 +65,14 @@ describe('ProgrammingExerciseGitDiffFilePanel Component', () => {
previousStartLine: 5,
previousLineCount: 1,
},
- ] as ProgrammingExerciseGitDiffEntry[];
- comp.ngOnInit();
- expect(comp.addedLineCount).toBe(4);
- expect(comp.removedLineCount).toBe(1);
+ ] as ProgrammingExerciseGitDiffEntry[]);
+ fixture.detectChanges();
+ expect(comp.addedLineCount()).toBe(4);
+ expect(comp.removedLineCount()).toBe(1);
});
it('Should set added/removed lines to 3-2', () => {
- comp.diffEntries = [
+ fixture.componentRef.setInput('diffEntries', [
{
filePath: 'src/a.java',
previousFilePath: 'src/a.java',
@@ -83,14 +81,14 @@ describe('ProgrammingExerciseGitDiffFilePanel Component', () => {
previousStartLine: 5,
previousLineCount: 2,
},
- ] as ProgrammingExerciseGitDiffEntry[];
- comp.ngOnInit();
- expect(comp.addedLineCount).toBe(3);
- expect(comp.removedLineCount).toBe(2);
+ ] as ProgrammingExerciseGitDiffEntry[]);
+ fixture.detectChanges();
+ expect(comp.addedLineCount()).toBe(3);
+ expect(comp.removedLineCount()).toBe(2);
});
it('Should set added/removed lines to 2-3', () => {
- comp.diffEntries = [
+ fixture.componentRef.setInput('diffEntries', [
{
filePath: 'src/a.java',
previousFilePath: 'src/a.java',
@@ -99,14 +97,14 @@ describe('ProgrammingExerciseGitDiffFilePanel Component', () => {
previousStartLine: 5,
previousLineCount: 3,
},
- ] as ProgrammingExerciseGitDiffEntry[];
- comp.ngOnInit();
- expect(comp.addedLineCount).toBe(2);
- expect(comp.removedLineCount).toBe(3);
+ ] as ProgrammingExerciseGitDiffEntry[]);
+ fixture.detectChanges();
+ expect(comp.addedLineCount()).toBe(2);
+ expect(comp.removedLineCount()).toBe(3);
});
it('Should set added/removed lines to 1-4', () => {
- comp.diffEntries = [
+ fixture.componentRef.setInput('diffEntries', [
{
filePath: 'src/a.java',
previousFilePath: 'src/a.java',
@@ -115,23 +113,23 @@ describe('ProgrammingExerciseGitDiffFilePanel Component', () => {
previousStartLine: 5,
previousLineCount: 4,
},
- ] as ProgrammingExerciseGitDiffEntry[];
- comp.ngOnInit();
- expect(comp.addedLineCount).toBe(1);
- expect(comp.removedLineCount).toBe(4);
+ ] as ProgrammingExerciseGitDiffEntry[]);
+ fixture.detectChanges();
+ expect(comp.addedLineCount()).toBe(1);
+ expect(comp.removedLineCount()).toBe(4);
});
it('Should set added/removed lines to 0-1', () => {
- comp.diffEntries = [
+ fixture.componentRef.setInput('diffEntries', [
{
filePath: 'src/a.java',
previousFilePath: 'src/a.java',
previousStartLine: 1,
previousLineCount: 1,
},
- ] as ProgrammingExerciseGitDiffEntry[];
- comp.ngOnInit();
- expect(comp.addedLineCount).toBe(0);
- expect(comp.removedLineCount).toBe(1);
+ ] as ProgrammingExerciseGitDiffEntry[]);
+ fixture.detectChanges();
+ expect(comp.addedLineCount()).toBe(0);
+ expect(comp.removedLineCount()).toBe(1);
});
});
diff --git a/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file.component.spec.ts b/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file.component.spec.ts
index 47d3e75710b4..17007596e8a8 100644
--- a/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file.component.spec.ts
+++ b/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file.component.spec.ts
@@ -18,7 +18,7 @@ describe('GitDiffFileComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ArtemisTestModule, MonacoDiffEditorComponent],
- declarations: [GitDiffFileComponent],
+ declarations: [],
providers: [],
}).compileComponents();
// Required because Monaco uses the ResizeObserver for the diff editor.
@@ -33,28 +33,20 @@ describe('GitDiffFileComponent', () => {
jest.restoreAllMocks();
});
- it.each([
- getDiffEntryWithPaths('same file', 'same file'),
- getDiffEntryWithPaths('old file', 'renamed file'),
- getDiffEntryWithPaths('deleted file', undefined),
- getDiffEntryWithPaths(undefined, 'created file'),
- ])('should infer file paths from the diff entries', (entry) => {
- comp.diffEntries = [entry];
- fixture.detectChanges();
- expect(comp.modifiedFilePath).toBe(entry.filePath);
- expect(comp.originalFilePath).toBe(entry.previousFilePath);
- });
-
it('should initialize the content of the diff editor', () => {
const fileName = 'some-changed-file.java';
const originalContent = 'some file content';
const modifiedContent = 'some changed file content';
- const setFileContentsStub = jest.spyOn(comp.monacoDiffEditor, 'setFileContents').mockImplementation();
+ const setFileContentsStub = jest.fn();
+ jest.spyOn(comp, 'monacoDiffEditor').mockReturnValue({ setFileContents: setFileContentsStub } as unknown as MonacoDiffEditorComponent);
const diffEntry = getDiffEntryWithPaths(fileName, fileName);
- comp.originalFileContent = originalContent;
- comp.modifiedFileContent = modifiedContent;
- comp.diffEntries = [diffEntry];
+ fixture.componentRef.setInput('diffEntries', [diffEntry]);
+ fixture.componentRef.setInput('originalFileContent', originalContent);
+ fixture.componentRef.setInput('originalFilePath', fileName);
+ fixture.componentRef.setInput('modifiedFileContent', modifiedContent);
+ fixture.componentRef.setInput('modifiedFilePath', fileName);
fixture.detectChanges();
expect(setFileContentsStub).toHaveBeenCalledExactlyOnceWith(originalContent, fileName, modifiedContent, fileName);
+ expect(comp.fileUnchanged()).toBeFalse();
});
});
diff --git a/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-line-stat.component.spec.ts b/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-line-stat.component.spec.ts
index 03a61f184b67..2ea540371804 100644
--- a/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-line-stat.component.spec.ts
+++ b/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-line-stat.component.spec.ts
@@ -4,7 +4,7 @@ import { GitDiffLineStatComponent } from 'app/exercises/programming/hestia/git-d
import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
import { MockPipe } from 'ng-mocks';
-describe('Git-Diff line-stat Component', () => {
+describe('GitDiffLineStatComponent', () => {
let comp: GitDiffLineStatComponent;
let fixture: ComponentFixture;
@@ -29,10 +29,10 @@ describe('Git-Diff line-stat Component', () => {
];
it.each(boxesTestTable)('Should show %s-%s boxes', (expectedAddedSquareCount, expectedRemovedSquareCount, addedLineCount, removedLineCount) => {
- comp.addedLineCount = addedLineCount;
- comp.removedLineCount = removedLineCount;
- comp.ngOnInit();
- expect(comp.addedSquareCount).toBe(expectedAddedSquareCount);
- expect(comp.removedSquareCount).toBe(expectedRemovedSquareCount);
+ fixture.componentRef.setInput('addedLineCount', addedLineCount);
+ fixture.componentRef.setInput('removedLineCount', removedLineCount);
+ fixture.detectChanges();
+ expect(comp.squareCounts().addedSquareCount).toBe(expectedAddedSquareCount);
+ expect(comp.squareCounts().removedSquareCount).toBe(expectedRemovedSquareCount);
});
});
diff --git a/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-modal.component.spec.ts b/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-modal.component.spec.ts
index cfae09afae32..7fc9a8693d0e 100644
--- a/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-modal.component.spec.ts
+++ b/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-modal.component.spec.ts
@@ -1,7 +1,7 @@
import { GitDiffReportComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report.component';
import { ArtemisTestModule } from '../../../test.module';
import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
-import { ComponentFixture, TestBed, fakeAsync, flush } from '@angular/core/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GitDiffReportModalComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component';
import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service';
import { of, throwError } from 'rxjs';
@@ -32,7 +32,7 @@ describe('GitDiffReportModalComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ArtemisTestModule],
- declarations: [GitDiffReportModalComponent, MockPipe(ArtemisTranslatePipe), MockComponent(GitDiffReportComponent)],
+ declarations: [MockPipe(ArtemisTranslatePipe), MockComponent(GitDiffReportComponent)],
}).compileComponents();
fixture = TestBed.createComponent(GitDiffReportModalComponent);
comp = fixture.componentInstance;
@@ -52,68 +52,80 @@ describe('GitDiffReportModalComponent', () => {
jest.restoreAllMocks();
});
- it('should call correct service method onInit', fakeAsync(() => {
+ const finishEffects = async () => {
+ // We need to wait for all asynchronous code to finish execution before we can expect the results.
+ // See https://stackoverflow.com/questions/44741102/how-to-make-jest-wait-for-all-asynchronous-code-to-finish-execution-before-expec/51045733#51045733
+ await new Promise(process.nextTick);
+ await fixture.whenStable();
+ };
+
+ it('should call correct service method onInit', async () => {
// case 1: diff for template and solution
- comp.report = { programmingExercise: { id: 1 }, participationIdForRightCommit: 3, rightCommitHash: 'abc' } as ProgrammingExerciseGitDiffReport;
- comp.diffForTemplateAndSolution = true;
- comp.ngOnInit();
- flush();
+ fixture.componentRef.setInput('report', { programmingExercise: { id: 1 }, participationIdForRightCommit: 3, rightCommitHash: 'abc' } as ProgrammingExerciseGitDiffReport);
+ fixture.componentRef.setInput('diffForTemplateAndSolution', true);
+ fixture.detectChanges();
+ await finishEffects();
expect(loadTemplateFilesSpy).toHaveBeenCalledExactlyOnceWith(1);
expect(loadSolutionFilesSpy).toHaveBeenCalledExactlyOnceWith(1);
expect(loadParticipationFilesSpy).not.toHaveBeenCalled();
- expect(comp.leftCommitFileContentByPath).toEqual(filesWithContentTemplate);
- expect(comp.rightCommitFileContentByPath).toEqual(filesWithContentSolution);
- }));
+ expect(comp.leftCommitFileContentByPath()).toEqual(filesWithContentTemplate);
+ expect(comp.rightCommitFileContentByPath()).toEqual(filesWithContentSolution);
+ });
- it('should retrieve files for template and submission if diffForTemplateAndSolution is false and firstParticipationId is undefined', () => {
- comp.report = { programmingExercise: { id: 1 }, participationIdForRightCommit: 3, rightCommitHash: 'abc' } as ProgrammingExerciseGitDiffReport;
+ it('should retrieve files for template and submission if diffForTemplateAndSolution is false and firstParticipationId is undefined', async () => {
+ fixture.componentRef.setInput('report', { programmingExercise: { id: 1 }, participationIdForRightCommit: 3, rightCommitHash: 'abc' } as ProgrammingExerciseGitDiffReport);
// case 2: diff for submission with template
- comp.diffForTemplateAndSolution = false;
- comp.ngOnInit();
+ fixture.componentRef.setInput('diffForTemplateAndSolution', false);
+ fixture.detectChanges();
+ await finishEffects();
expect(loadParticipationFilesSpy).toHaveBeenCalledExactlyOnceWith(3, 'abc');
expect(loadTemplateFilesSpy).toHaveBeenCalledExactlyOnceWith(1);
- expect(comp.leftCommitFileContentByPath).toEqual(filesWithContentTemplate);
- expect(comp.rightCommitFileContentByPath).toEqual(filesWithContentParticipation1);
+ expect(comp.leftCommitFileContentByPath()).toEqual(filesWithContentTemplate);
+ expect(comp.rightCommitFileContentByPath()).toEqual(filesWithContentParticipation1);
});
- it('should retrieve files for both submissions if diffForTemplateAndSolution is false and firstParticipationId is defined', function () {
+ it('should retrieve files for both submissions if diffForTemplateAndSolution is false and firstParticipationId is defined', async () => {
// case 3: diff for two submissions
- comp.report = {
+ fixture.componentRef.setInput('report', {
programmingExercise: { id: 1 },
participationIdForRightCommit: 3,
rightCommitHash: 'abc',
participationIdForLeftCommit: 2,
leftCommitHash: 'def',
- } as ProgrammingExerciseGitDiffReport;
- comp.diffForTemplateAndSolution = false;
- comp.ngOnInit();
+ } as ProgrammingExerciseGitDiffReport);
+ fixture.componentRef.setInput('diffForTemplateAndSolution', false);
+ fixture.detectChanges();
+ await finishEffects();
expect(loadParticipationFilesSpy).toHaveBeenCalledTimes(2);
expect(loadParticipationFilesSpy).toHaveBeenCalledWith(2, 'def');
expect(loadParticipationFilesSpy).toHaveBeenCalledWith(3, 'abc');
- expect(comp.leftCommitFileContentByPath).toEqual(filesWithContentParticipation1);
- expect(comp.rightCommitFileContentByPath).toEqual(filesWithContentParticipation1);
+ expect(comp.leftCommitFileContentByPath()).toEqual(filesWithContentParticipation1);
+ expect(comp.rightCommitFileContentByPath()).toEqual(filesWithContentParticipation1);
});
- it('should set error flag if loading files fails', () => {
+ it('should set error flag if loading files fails', async () => {
jest.spyOn(programmingExerciseService, 'getTemplateRepositoryTestFilesWithContent').mockReturnValue(throwError('error'));
jest.spyOn(programmingExerciseService, 'getSolutionRepositoryTestFilesWithContent').mockReturnValue(throwError('error'));
jest.spyOn(programmingExerciseParticipationService, 'getParticipationRepositoryFilesWithContentAtCommit').mockReturnValue(throwError('error'));
- comp.report = { programmingExercise: { id: 1 }, participationIdForRightCommit: 3, rightCommitHash: 'abc' } as ProgrammingExerciseGitDiffReport;
- comp.diffForTemplateAndSolution = true;
- comp.ngOnInit();
- expect(comp.errorWhileFetchingRepos).toBeTrue();
+ fixture.componentRef.setInput('report', { programmingExercise: { id: 1 }, participationIdForRightCommit: 3, rightCommitHash: 'abc' } as ProgrammingExerciseGitDiffReport);
+ fixture.componentRef.setInput('diffForTemplateAndSolution', true);
+ fixture.detectChanges();
+ await fixture.whenStable();
+ expect(comp.errorWhileFetchingRepos()).toBeTrue();
//reset value
- comp.errorWhileFetchingRepos = false;
- comp.diffForTemplateAndSolution = false;
- comp.ngOnInit();
- expect(comp.errorWhileFetchingRepos).toBeTrue();
+ comp.errorWhileFetchingRepos.set(false);
+ fixture.componentRef.setInput('diffForTemplateAndSolution', false);
+ fixture.detectChanges();
+ await fixture.whenStable();
+ expect(comp.errorWhileFetchingRepos()).toBeTrue();
//reset value
- comp.errorWhileFetchingRepos = false;
- comp.report = { ...comp.report, participationIdForLeftCommit: 2, leftCommitHash: 'def' } as ProgrammingExerciseGitDiffReport;
- comp.ngOnInit();
- expect(comp.errorWhileFetchingRepos).toBeTrue();
+ comp.errorWhileFetchingRepos.set(false);
+ fixture.componentRef.setInput('report', { ...comp.report, participationIdForLeftCommit: 2, leftCommitHash: 'def' } as unknown as ProgrammingExerciseGitDiffReport);
+ fixture.detectChanges();
+ await fixture.whenStable();
+ expect(comp.errorWhileFetchingRepos()).toBeTrue();
});
it('should call modal service when close() is invoked', () => {
@@ -122,36 +134,38 @@ describe('GitDiffReportModalComponent', () => {
expect(modalServiceSpy).toHaveBeenCalled();
});
- it('should load files from cache if available for template and participation repo', () => {
+ it('should load files from cache if available for template and participation repo', async () => {
const cachedRepositoryFiles = new Map>();
cachedRepositoryFiles.set('1-template', filesWithContentTemplate);
cachedRepositoryFiles.set('def', filesWithContentParticipation1);
- comp.cachedRepositoryFiles = cachedRepositoryFiles;
- comp.report = { programmingExercise: { id: 1 }, participationIdForRightCommit: 3, rightCommitHash: 'def' } as ProgrammingExerciseGitDiffReport;
- comp.diffForTemplateAndSolution = false;
- comp.ngOnInit();
+ fixture.componentRef.setInput('cachedRepositoryFiles', cachedRepositoryFiles);
+ fixture.componentRef.setInput('report', { programmingExercise: { id: 1 }, participationIdForRightCommit: 3, rightCommitHash: 'def' } as ProgrammingExerciseGitDiffReport);
+ fixture.componentRef.setInput('diffForTemplateAndSolution', false);
+ fixture.detectChanges();
+ await finishEffects();
expect(loadParticipationFilesSpy).not.toHaveBeenCalled();
expect(loadTemplateFilesSpy).not.toHaveBeenCalled();
- expect(comp.leftCommitFileContentByPath).toEqual(filesWithContentTemplate);
- expect(comp.rightCommitFileContentByPath).toEqual(filesWithContentParticipation1);
+ expect(comp.leftCommitFileContentByPath()).toEqual(filesWithContentTemplate);
+ expect(comp.rightCommitFileContentByPath()).toEqual(filesWithContentParticipation1);
});
- it('should load files from cache if available for participation repo at both commits', () => {
+ it('should load files from cache if available for participation repo at both commits', async () => {
const cachedRepositoryFiles = new Map>();
cachedRepositoryFiles.set('def', filesWithContentParticipation1);
cachedRepositoryFiles.set('abc', filesWithContentParticipation2);
- comp.cachedRepositoryFiles = cachedRepositoryFiles;
- comp.report = {
+ fixture.componentRef.setInput('cachedRepositoryFiles', cachedRepositoryFiles);
+ fixture.componentRef.setInput('report', {
programmingExercise: { id: 1 },
participationIdForRightCommit: 3,
rightCommitHash: 'abc',
participationIdForLeftCommit: 2,
leftCommitHash: 'def',
- } as ProgrammingExerciseGitDiffReport;
- comp.diffForTemplateAndSolution = false;
- comp.ngOnInit();
+ } as ProgrammingExerciseGitDiffReport);
+ fixture.componentRef.setInput('diffForTemplateAndSolution', false);
+ fixture.detectChanges();
+ await finishEffects();
expect(loadParticipationFilesSpy).not.toHaveBeenCalled();
- expect(comp.leftCommitFileContentByPath).toEqual(filesWithContentParticipation1);
- expect(comp.rightCommitFileContentByPath).toEqual(filesWithContentParticipation2);
+ expect(comp.leftCommitFileContentByPath()).toEqual(filesWithContentParticipation1);
+ expect(comp.rightCommitFileContentByPath()).toEqual(filesWithContentParticipation2);
});
});
diff --git a/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-report.component.spec.ts b/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-report.component.spec.ts
index 18614f41c481..143c150edc57 100644
--- a/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-report.component.spec.ts
+++ b/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-report.component.spec.ts
@@ -8,7 +8,6 @@ import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programmin
import { ProgrammingExerciseGitDiffEntry } from 'app/entities/hestia/programming-exercise-git-diff-entry.model';
import { NgbTooltipMocksModule } from '../../../helpers/mocks/directive/ngbTooltipMocks.module';
import { GitDiffFilePanelComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component';
-import { ButtonComponent } from 'app/shared/components/button.component';
describe('ProgrammingExerciseGitDiffReport Component', () => {
let comp: GitDiffReportComponent;
@@ -17,19 +16,19 @@ describe('ProgrammingExerciseGitDiffReport Component', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ArtemisTestModule, NgbTooltipMocksModule],
- declarations: [
- GitDiffReportComponent,
- MockComponent(ButtonComponent),
- MockPipe(ArtemisTranslatePipe),
- MockComponent(GitDiffFilePanelComponent),
- MockComponent(GitDiffLineStatComponent),
- ],
+ declarations: [GitDiffReportComponent, MockPipe(ArtemisTranslatePipe), MockComponent(GitDiffFilePanelComponent), MockComponent(GitDiffLineStatComponent)],
providers: [],
}).compileComponents();
fixture = TestBed.createComponent(GitDiffReportComponent);
comp = fixture.componentInstance;
- comp.templateFileContentByPath = new Map([['src/a.java', 'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16']]);
- comp.solutionFileContentByPath = new Map([['src/a.java', 'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16']]);
+ fixture.componentRef.setInput(
+ 'templateFileContentByPath',
+ new Map([['src/a.java', 'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16']]),
+ );
+ fixture.componentRef.setInput(
+ 'solutionFileContentByPath',
+ new Map([['src/a.java', 'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16']]),
+ );
});
afterEach(() => {
@@ -55,18 +54,18 @@ describe('ProgrammingExerciseGitDiffReport Component', () => {
{ filePath: 'src/b.java', startLine: 2 },
] as ProgrammingExerciseGitDiffEntry[];
- comp.report = { entries } as ProgrammingExerciseGitDiffReport;
- comp.ngOnInit();
- expect(comp.entries).toStrictEqual(expectedEntries);
+ fixture.componentRef.setInput('report', { entries } as ProgrammingExerciseGitDiffReport);
+ fixture.detectChanges();
+ expect(comp.sortedEntries()).toStrictEqual(expectedEntries);
});
it('Should set added/removed lines to 1-0', () => {
const entries = [{ filePath: 'src/a.java', previousFilePath: 'src/a.java', startLine: 1, lineCount: 1 }] as ProgrammingExerciseGitDiffEntry[];
- comp.report = { entries } as ProgrammingExerciseGitDiffReport;
- comp.ngOnInit();
- expect(comp.addedLineCount).toBe(1);
- expect(comp.removedLineCount).toBe(0);
+ fixture.componentRef.setInput('report', { entries } as ProgrammingExerciseGitDiffReport);
+ fixture.detectChanges();
+ expect(comp.addedLineCount()).toBe(1);
+ expect(comp.removedLineCount()).toBe(0);
});
it('Should set added/removed lines to 4-1', () => {
@@ -81,10 +80,10 @@ describe('ProgrammingExerciseGitDiffReport Component', () => {
},
] as ProgrammingExerciseGitDiffEntry[];
- comp.report = { entries } as ProgrammingExerciseGitDiffReport;
- comp.ngOnInit();
- expect(comp.addedLineCount).toBe(4);
- expect(comp.removedLineCount).toBe(1);
+ fixture.componentRef.setInput('report', { entries } as ProgrammingExerciseGitDiffReport);
+ fixture.detectChanges();
+ expect(comp.addedLineCount()).toBe(4);
+ expect(comp.removedLineCount()).toBe(1);
});
it('Should set added/removed lines to 3-2', () => {
@@ -99,10 +98,10 @@ describe('ProgrammingExerciseGitDiffReport Component', () => {
},
] as ProgrammingExerciseGitDiffEntry[];
- comp.report = { entries } as ProgrammingExerciseGitDiffReport;
- comp.ngOnInit();
- expect(comp.addedLineCount).toBe(3);
- expect(comp.removedLineCount).toBe(2);
+ fixture.componentRef.setInput('report', { entries } as ProgrammingExerciseGitDiffReport);
+ fixture.detectChanges();
+ expect(comp.addedLineCount()).toBe(3);
+ expect(comp.removedLineCount()).toBe(2);
});
it('Should set added/removed lines to 2-3', () => {
@@ -117,10 +116,10 @@ describe('ProgrammingExerciseGitDiffReport Component', () => {
},
] as ProgrammingExerciseGitDiffEntry[];
- comp.report = { entries } as ProgrammingExerciseGitDiffReport;
- comp.ngOnInit();
- expect(comp.addedLineCount).toBe(2);
- expect(comp.removedLineCount).toBe(3);
+ fixture.componentRef.setInput('report', { entries } as ProgrammingExerciseGitDiffReport);
+ fixture.detectChanges();
+ expect(comp.addedLineCount()).toBe(2);
+ expect(comp.removedLineCount()).toBe(3);
});
it('Should set added/removed lines to 1-4', () => {
@@ -135,19 +134,19 @@ describe('ProgrammingExerciseGitDiffReport Component', () => {
},
] as ProgrammingExerciseGitDiffEntry[];
- comp.report = { entries } as ProgrammingExerciseGitDiffReport;
- comp.ngOnInit();
- expect(comp.addedLineCount).toBe(1);
- expect(comp.removedLineCount).toBe(4);
+ fixture.componentRef.setInput('report', { entries } as ProgrammingExerciseGitDiffReport);
+ fixture.detectChanges();
+ expect(comp.addedLineCount()).toBe(1);
+ expect(comp.removedLineCount()).toBe(4);
});
it('Should set added/removed lines to 0-1', () => {
const entries = [{ filePath: 'src/a.java', previousFilePath: 'src/a.java', previousStartLine: 1, previousLineCount: 1 }] as ProgrammingExerciseGitDiffEntry[];
- comp.report = { entries } as ProgrammingExerciseGitDiffReport;
- comp.ngOnInit();
- expect(comp.addedLineCount).toBe(0);
- expect(comp.removedLineCount).toBe(1);
+ fixture.componentRef.setInput('report', { entries } as ProgrammingExerciseGitDiffReport);
+ fixture.detectChanges();
+ expect(comp.addedLineCount()).toBe(0);
+ expect(comp.removedLineCount()).toBe(1);
});
it('should record for each path whether the diff is ready', () => {
@@ -161,26 +160,30 @@ describe('ProgrammingExerciseGitDiffReport Component', () => {
{ filePath: 'src/b.java', startLine: 2 },
{ filePath: 'src/a.java', startLine: 1 },
];
- comp.solutionFileContentByPath = new Map();
- comp.solutionFileContentByPath.set(filePath1, 'some file content');
- comp.solutionFileContentByPath.set(filePath2, 'some other file content');
- comp.templateFileContentByPath = comp.solutionFileContentByPath;
- comp.report = { entries } as ProgrammingExerciseGitDiffReport;
+ fixture.componentRef.setInput(
+ 'solutionFileContentByPath',
+ new Map([
+ ['src/a.java', 'some file content'],
+ ['src/b.java', 'some other file content'],
+ ]),
+ );
+ fixture.componentRef.setInput('templateFileContentByPath', comp.solutionFileContentByPath());
+ fixture.componentRef.setInput('report', { entries } as ProgrammingExerciseGitDiffReport);
fixture.detectChanges();
// Initialization
- expect(comp.allDiffsReady).toBeFalse();
- expect(comp.diffInformationForPaths[0].diffReady).toBeFalse();
- expect(comp.diffInformationForPaths[1].diffReady).toBeFalse();
+ expect(comp.allDiffsReady()).toBeFalse();
+ expect(comp.diffInformationForPaths()[0].diffReady).toBeFalse();
+ expect(comp.diffInformationForPaths()[1].diffReady).toBeFalse();
// First file ready
comp.onDiffReady(filePath1, true);
- expect(comp.allDiffsReady).toBeFalse();
- expect(comp.diffInformationForPaths[0].diffReady).toBeTrue();
- expect(comp.diffInformationForPaths[1].diffReady).toBeFalse();
+ expect(comp.allDiffsReady()).toBeFalse();
+ expect(comp.diffInformationForPaths()[0].diffReady).toBeTrue();
+ expect(comp.diffInformationForPaths()[1].diffReady).toBeFalse();
// Second file ready
comp.onDiffReady(filePath2, true);
- expect(comp.allDiffsReady).toBeTrue();
- expect(comp.diffInformationForPaths[0].diffReady).toBeTrue();
- expect(comp.diffInformationForPaths[1].diffReady).toBeTrue();
+ expect(comp.allDiffsReady()).toBeTrue();
+ expect(comp.diffInformationForPaths()[0].diffReady).toBeTrue();
+ expect(comp.diffInformationForPaths()[1].diffReady).toBeTrue();
});
it('should correctly identify renamed files', () => {
@@ -196,18 +199,18 @@ describe('ProgrammingExerciseGitDiffReport Component', () => {
];
const defaultContent = 'some content that might change';
const modifiedContent = 'some content that has changed';
- comp.report = { entries } as ProgrammingExerciseGitDiffReport;
+ fixture.componentRef.setInput('report', { entries } as ProgrammingExerciseGitDiffReport);
const originalFileContents = new Map();
const modifiedFileContents = new Map();
[originalFilePath1, originalFilePath2, notRenamedFilePath].forEach((path) => {
originalFileContents.set(path, defaultContent);
modifiedFileContents.set(path, path === originalFilePath1 ? defaultContent : modifiedContent);
});
- comp.templateFileContentByPath = originalFileContents;
- comp.solutionFileContentByPath = modifiedFileContents;
+ fixture.componentRef.setInput('templateFileContentByPath', originalFileContents);
+ fixture.componentRef.setInput('solutionFileContentByPath', modifiedFileContents);
fixture.detectChanges();
- expect(comp.renamedFilePaths).toEqual({ [renamedFilePath1]: originalFilePath1, [renamedFilePath2]: originalFilePath2 });
+ expect(comp.renamedFilePaths()).toEqual({ [renamedFilePath1]: originalFilePath1, [renamedFilePath2]: originalFilePath2 });
});
});
diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-diff-editor.component.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-diff-editor.component.spec.ts
index 459c093e3896..bdfd70b64723 100644
--- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-diff-editor.component.spec.ts
+++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-diff-editor.component.spec.ts
@@ -27,22 +27,19 @@ describe('MonacoDiffEditorComponent', () => {
it('should dispose its listeners and subscriptions when destroyed', () => {
fixture.detectChanges();
- const resizeObserverDisconnectSpy = jest.spyOn(comp.resizeObserver!, 'disconnect');
const listenerDisposeSpies = comp.listeners.map((listener) => jest.spyOn(listener, 'dispose'));
comp.ngOnDestroy();
- for (const spy of [resizeObserverDisconnectSpy, ...listenerDisposeSpies]) {
+ for (const spy of listenerDisposeSpies) {
expect(spy).toHaveBeenCalledOnce();
}
});
- it('should update the size of its container and layout the editor', () => {
- const layoutSpy = jest.spyOn(comp, 'layout');
+ it('should update the size of its container', () => {
fixture.detectChanges();
const element = document.createElement('div');
comp.monacoDiffEditorContainerElement = element;
- comp.adjustHeightAndLayout(100);
+ comp.adjustContainerHeight(100);
expect(element.style.height).toBe('100px');
- expect(layoutSpy).toHaveBeenCalledOnce();
});
it('should set the text of the editor', () => {