diff --git a/src/main/webapp/app/detail-overview-list/detail-overview-list.component.html b/src/main/webapp/app/detail-overview-list/detail-overview-list.component.html
index 11e5ee4c828f..ba2349c564d3 100644
--- a/src/main/webapp/app/detail-overview-list/detail-overview-list.component.html
+++ b/src/main/webapp/app/detail-overview-list/detail-overview-list.component.html
@@ -1,7 +1,7 @@
@if (headlines?.length && headlines.length > 1) {
}
-@for (section of sections; track section) {
+@for (section of sections(); track section) {
{{ section.headline | artemisTranslate }}
@for (detail of section.details; track $index) {
diff --git a/src/main/webapp/app/detail-overview-list/detail-overview-list.component.ts b/src/main/webapp/app/detail-overview-list/detail-overview-list.component.ts
index 25a5a6ca72e6..27eeca162243 100644
--- a/src/main/webapp/app/detail-overview-list/detail-overview-list.component.ts
+++ b/src/main/webapp/app/detail-overview-list/detail-overview-list.component.ts
@@ -1,7 +1,7 @@
-import { Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
+import { Component, OnDestroy, OnInit, ViewEncapsulation, inject, input } from '@angular/core';
import { isEmpty } from 'lodash-es';
import { FeatureToggle } from 'app/shared/feature-toggle/feature-toggle.service';
-import { ButtonSize, TooltipPlacement } from 'app/shared/components/button.component';
+import { ButtonSize } from 'app/shared/components/button.component';
import { IrisSubSettingsType } from 'app/entities/iris/settings/iris-sub-settings.model';
import { ModelingExerciseService } from 'app/exercises/modeling/manage/modeling-exercise.service';
import { AlertService } from 'app/core/util/alert.service';
@@ -50,11 +50,13 @@ export class DetailOverviewListComponent implements OnInit, OnDestroy {
protected readonly FeatureToggle = FeatureToggle;
protected readonly ButtonSize = ButtonSize;
protected readonly ProgrammingExerciseParticipationType = ProgrammingExerciseParticipationType;
+ protected readonly CHAT = IrisSubSettingsType.CHAT;
- readonly CHAT = IrisSubSettingsType.CHAT;
+ private readonly modelingExerciseService = inject(ModelingExerciseService);
+ private readonly alertService = inject(AlertService);
+ private readonly profileService = inject(ProfileService);
- @Input()
- sections: DetailOverviewSection[];
+ sections = input.required();
// headline list for navigation bar
headlines: { id: string; translationKey: string }[];
@@ -64,14 +66,8 @@ export class DetailOverviewListComponent implements OnInit, OnDestroy {
profileSubscription: Subscription;
isLocalVC = false;
- constructor(
- private modelingExerciseService: ModelingExerciseService,
- private alertService: AlertService,
- private profileService: ProfileService,
- ) {}
-
ngOnInit() {
- this.headlines = this.sections.map((section) => {
+ this.headlines = this.sections().map((section) => {
return {
id: section.headline.replaceAll('.', '-'),
translationKey: section.headline,
@@ -98,6 +94,4 @@ export class DetailOverviewListComponent implements OnInit, OnDestroy {
ngOnDestroy() {
this.profileSubscription?.unsubscribe();
}
-
- protected readonly TooltipPlacement = TooltipPlacement;
}
diff --git a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts
index 1a1a95462530..df3eec824ca7 100644
--- a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts
+++ b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts
@@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { SafeHtml } from '@angular/platform-browser';
import { ProgrammingExerciseBuildConfig } from 'app/entities/programming/programming-exercise-build.config';
-import { Subject, Subscription } from 'rxjs';
+import { Subject, Subscription, of } from 'rxjs';
import { ProgrammingExercise, ProgrammingLanguage } from 'app/entities/programming/programming-exercise.model';
import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service';
import { AlertService, AlertType } from 'app/core/util/alert.service';
@@ -57,6 +57,9 @@ import { IrisSubSettingsType } from 'app/entities/iris/settings/iris-sub-setting
import { Detail } from 'app/detail-overview-list/detail.model';
import { Competency } from 'app/entities/competency.model';
import { AeolusService } from 'app/exercises/programming/shared/service/aeolus.service';
+import { switchMap, tap } from 'rxjs/operators';
+import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model';
+import { BuildLogStatisticsDTO } from 'app/entities/programming/build-log-statistics-dto';
@Component({
selector: 'jhi-programming-exercise-detail',
@@ -184,13 +187,15 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy {
this.templateAndSolutionParticipationSubscription = this.programmingExerciseService
.findWithTemplateAndSolutionParticipationAndLatestResults(programmingExercise.id!)
- .subscribe((updatedProgrammingExercise) => {
- this.programmingExercise = updatedProgrammingExercise.body!;
-
- this.setLatestCoveredLineRatio();
- this.loadingTemplateParticipationResults = false;
- this.loadingSolutionParticipationResults = false;
- this.profileInfoSubscription = this.profileService.getProfileInfo().subscribe(async (profileInfo) => {
+ .pipe(
+ tap((updatedProgrammingExercise) => {
+ this.programmingExercise = updatedProgrammingExercise.body!;
+ this.setLatestCoveredLineRatio();
+ this.loadingTemplateParticipationResults = false;
+ this.loadingSolutionParticipationResults = false;
+ }),
+ switchMap(() => this.profileService.getProfileInfo()),
+ tap(async (profileInfo) => {
if (profileInfo) {
if (this.programmingExercise.projectKey && this.programmingExercise.templateParticipation?.buildPlanId) {
this.programmingExercise.templateParticipation.buildPlanUrl = createBuildPlanUrl(
@@ -215,38 +220,39 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy {
if (this.irisEnabled) {
this.irisSettingsSubscription = this.irisSettingsService.getCombinedCourseSettings(this.courseId).subscribe((settings) => {
this.irisChatEnabled = settings?.irisChatSettings?.enabled ?? false;
- this.exerciseDetailSections = this.getExerciseDetails();
});
}
}
+ }),
+ switchMap(() => this.programmingExerciseSubmissionPolicyService.getSubmissionPolicyOfProgrammingExercise(exerciseId!)),
+ tap((submissionPolicy) => {
+ this.programmingExercise.submissionPolicy = submissionPolicy;
+ }),
+ switchMap(() => this.programmingExerciseService.getDiffReport(this.programmingExercise.id!)),
+ tap((gitDiffReport) => {
+ this.processGitDiffReport(gitDiffReport);
+ }),
+ switchMap(() =>
+ this.programmingExercise.isAtLeastEditor ? this.programmingExerciseService.getBuildLogStatistics(exerciseId!) : of([] as BuildLogStatisticsDTO),
+ ),
+ tap((buildLogStatistics) => {
+ if (this.programmingExercise.isAtLeastEditor) {
+ this.programmingExercise.buildLogStatistics = buildLogStatistics;
+ }
+ }),
+ )
+ .subscribe({
+ next: () => {
+ this.setLatestCoveredLineRatio();
+ this.checkAndAlertInconsistencies();
+ this.plagiarismCheckSupported = this.programmingLanguageFeatureService.getProgrammingLanguageFeature(
+ programmingExercise.programmingLanguage,
+ ).plagiarismCheckSupported;
this.exerciseDetailSections = this.getExerciseDetails();
- });
-
- this.submissionPolicySubscription = this.programmingExerciseSubmissionPolicyService
- .getSubmissionPolicyOfProgrammingExercise(exerciseId!)
- .subscribe((submissionPolicy) => {
- this.programmingExercise.submissionPolicy = submissionPolicy;
- this.exerciseDetailSections = this.getExerciseDetails();
- });
-
- this.loadGitDiffReport();
-
- // the build logs endpoint requires at least editor privileges
- if (this.programmingExercise.isAtLeastEditor) {
- this.buildLogsSubscription = this.programmingExerciseService
- .getBuildLogStatistics(exerciseId!)
- .subscribe((buildLogStatistics) => (this.programmingExercise.buildLogStatistics = buildLogStatistics));
- this.exerciseDetailSections = this.getExerciseDetails();
- }
-
- this.setLatestCoveredLineRatio();
-
- this.checkAndAlertInconsistencies();
-
- this.plagiarismCheckSupported = this.programmingLanguageFeatureService.getProgrammingLanguageFeature(
- programmingExercise.programmingLanguage,
- ).plagiarismCheckSupported;
- this.exerciseDetailSections = this.getExerciseDetails();
+ },
+ error: (error) => {
+ this.alertService.error(error.message);
+ },
});
this.exerciseStatisticsSubscription = this.statisticsService.getExerciseStatistics(exerciseId!).subscribe((statistics: ExerciseManagementStatisticsDto) => {
@@ -780,29 +786,27 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy {
return link;
}
+ private processGitDiffReport(gitDiffReport: ProgrammingExerciseGitDiffReport | undefined): void {
+ if (
+ gitDiffReport &&
+ (this.programmingExercise.gitDiffReport?.templateRepositoryCommitHash !== gitDiffReport.templateRepositoryCommitHash ||
+ this.programmingExercise.gitDiffReport?.solutionRepositoryCommitHash !== gitDiffReport.solutionRepositoryCommitHash)
+ ) {
+ this.programmingExercise.gitDiffReport = gitDiffReport;
+ gitDiffReport.programmingExercise = this.programmingExercise;
+
+ const calculateLineCount = (entries: { lineCount?: number; previousLineCount?: number }[] = [], key: 'lineCount' | 'previousLineCount') =>
+ entries.map((entry) => entry[key] ?? 0).reduce((sum, count) => sum + count, 0);
+
+ this.addedLineCount = calculateLineCount(gitDiffReport.entries, 'lineCount');
+ this.removedLineCount = calculateLineCount(gitDiffReport.entries, 'previousLineCount');
+ }
+ }
+
loadGitDiffReport() {
this.programmingExerciseService.getDiffReport(this.programmingExercise.id!).subscribe((gitDiffReport) => {
- if (
- gitDiffReport &&
- (this.programmingExercise.gitDiffReport?.templateRepositoryCommitHash !== gitDiffReport.templateRepositoryCommitHash ||
- this.programmingExercise.gitDiffReport?.solutionRepositoryCommitHash !== gitDiffReport.solutionRepositoryCommitHash)
- ) {
- this.programmingExercise.gitDiffReport = gitDiffReport;
- gitDiffReport.programmingExercise = this.programmingExercise;
- this.addedLineCount =
- gitDiffReport.entries
- ?.map((entry) => entry.lineCount)
- .filter((lineCount) => lineCount)
- .map((lineCount) => lineCount!)
- .reduce((lineCount1, lineCount2) => lineCount1 + lineCount2, 0) ?? 0;
- this.removedLineCount =
- gitDiffReport.entries
- ?.map((entry) => entry.previousLineCount)
- .filter((lineCount) => lineCount)
- .map((lineCount) => lineCount!)
- .reduce((lineCount1, lineCount2) => lineCount1 + lineCount2, 0) ?? 0;
- this.exerciseDetailSections = this.getExerciseDetails();
- }
+ this.processGitDiffReport(gitDiffReport);
+ this.exerciseDetailSections = this.getExerciseDetails();
});
}
diff --git a/src/test/javascript/spec/component/detail-overview-list.component.spec.ts b/src/test/javascript/spec/component/detail-overview-list.component.spec.ts
index 61e891b11e35..8d5998e3053a 100644
--- a/src/test/javascript/spec/component/detail-overview-list.component.spec.ts
+++ b/src/test/javascript/spec/component/detail-overview-list.component.spec.ts
@@ -56,7 +56,7 @@ describe('DetailOverviewList', () => {
});
it('should initialize and destroy', () => {
- component.sections = sections;
+ fixture.componentRef.setInput('sections', sections);
fixture.detectChanges();
expect(component.headlines).toStrictEqual([{ id: 'headline-1', translationKey: 'headline.1' }]);
expect(component.headlinesRecord).toStrictEqual({ 'headline.1': 'headline-1' });
@@ -67,7 +67,7 @@ describe('DetailOverviewList', () => {
});
it('should escape all falsy values', () => {
- component.sections = [
+ fixture.componentRef.setInput('sections', [
{
headline: 'some-section',
details: [
@@ -81,7 +81,7 @@ describe('DetailOverviewList', () => {
},
],
},
- ];
+ ]);
fixture.detectChanges();
const detailListTitleDOMElements = fixture.nativeElement.querySelectorAll('dt[id^=detail-title]');
expect(detailListTitleDOMElements).toHaveLength(1);
diff --git a/src/test/javascript/spec/component/programming-exercise/programming-exercise-detail.component.spec.ts b/src/test/javascript/spec/component/programming-exercise/programming-exercise-detail.component.spec.ts
index 8563723b42b8..4fc8013ced0e 100644
--- a/src/test/javascript/spec/component/programming-exercise/programming-exercise-detail.component.spec.ts
+++ b/src/test/javascript/spec/component/programming-exercise/programming-exercise-detail.component.spec.ts
@@ -22,9 +22,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service';
import { LocalStorageService, SessionStorageService } from 'ngx-webstorage';
import { MockProgrammingExerciseGradingService } from '../../helpers/mocks/service/mock-programming-exercise-grading.service';
-import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model';
import { ProgrammingExerciseSolutionEntry } from 'app/entities/hestia/programming-exercise-solution-entry.model';
-import { BuildLogStatisticsDTO } from 'app/entities/programming/build-log-statistics-dto';
import { TemplateProgrammingExerciseParticipation } from 'app/entities/participation/template-programming-exercise-participation.model';
import { SolutionProgrammingExerciseParticipation } from 'app/entities/participation/solution-programming-exercise-participation.model';
import { HttpResponse } from '@angular/common/http';
@@ -34,8 +32,9 @@ import {
ProgrammingLanguageFeatureService,
} from 'app/exercises/programming/shared/service/programming-language-feature/programming-language-feature.service';
import { MockRouter } from '../../helpers/mocks/mock-router';
+import { BuildConfig } from '../../../../../main/webapp/app/entities/programming/build-config.model';
-describe('ProgrammingExercise Management Detail Component', () => {
+describe('ProgrammingExerciseDetailComponent', () => {
let comp: ProgrammingExerciseDetailComponent;
let fixture: ComponentFixture;
let statisticsService: StatisticsService;
@@ -44,8 +43,6 @@ describe('ProgrammingExercise Management Detail Component', () => {
let profileService: ProfileService;
let programmingLanguageFeatureService: ProgrammingLanguageFeatureService;
let statisticsServiceStub: jest.SpyInstance;
- let gitDiffReportStub: jest.SpyInstance;
- let buildLogStatisticsStub: jest.SpyInstance;
let findWithTemplateAndSolutionParticipationStub: jest.SpyInstance;
let router: Router;
let modalService: NgbModal;
@@ -59,6 +56,9 @@ describe('ProgrammingExercise Management Detail Component', () => {
solutionParticipation: {
id: 2,
} as SolutionProgrammingExerciseParticipation,
+ buildConfig: {
+ testwiseCoverageEnabled: true,
+ } as BuildConfig,
} as ProgrammingExercise;
const exerciseStatistics = {
@@ -75,30 +75,6 @@ describe('ProgrammingExercise Management Detail Component', () => {
resolvedPostsInPercent: 50,
} as ExerciseManagementStatisticsDto;
- const gitDiffReport = {
- templateRepositoryCommitHash: 'x1',
- solutionRepositoryCommitHash: 'x2',
- entries: [
- {
- previousFilePath: '/src/test.java',
- filePath: '/src/test.java',
- previousStartLine: 1,
- startLine: 1,
- previousLineCount: 2,
- lineCount: 2,
- },
- ],
- } as ProgrammingExerciseGitDiffReport;
-
- const buildLogStatistics = {
- buildCount: 5,
- agentSetupDuration: 2.5,
- testDuration: 3,
- scaDuration: 2,
- totalJobDuration: 7.5,
- dependenciesDownloadedCount: 6,
- } as BuildLogStatisticsDTO;
-
const profileInfo = {
activeProfiles: [],
} as unknown as ProfileInfo;
@@ -135,8 +111,6 @@ describe('ProgrammingExercise Management Detail Component', () => {
findWithTemplateAndSolutionParticipationStub = jest
.spyOn(exerciseService, 'findWithTemplateAndSolutionParticipationAndLatestResults')
.mockReturnValue(of(new HttpResponse({ body: mockProgrammingExercise })));
- gitDiffReportStub = jest.spyOn(exerciseService, 'getDiffReport').mockReturnValue(of(gitDiffReport));
- buildLogStatisticsStub = jest.spyOn(exerciseService, 'getBuildLogStatistics').mockReturnValue(of(buildLogStatistics));
jest.spyOn(profileService, 'getProfileInfo').mockReturnValue(of(profileInfo));
jest.spyOn(programmingLanguageFeatureService, 'getProgrammingLanguageFeature').mockReturnValue({
@@ -148,6 +122,18 @@ describe('ProgrammingExercise Management Detail Component', () => {
jest.restoreAllMocks();
});
+ it('should reload on participation change', fakeAsync(() => {
+ const loadDiffSpy = jest.spyOn(comp, 'loadGitDiffReport');
+ jest.spyOn(exerciseService, 'getLatestResult').mockReturnValue({ successful: true });
+ jest.spyOn(exerciseService, 'getLatestFullTestwiseCoverageReport').mockReturnValue(of({ coveredLineRatio: 0.5 }));
+ comp.programmingExercise = mockProgrammingExercise;
+ comp.programmingExerciseBuildConfig = mockProgrammingExercise.buildConfig;
+ comp.onParticipationChange();
+ tick();
+ expect(loadDiffSpy).toHaveBeenCalledOnce();
+ expect(comp.programmingExercise.coveredLinesRatio).toBe(0.5);
+ }));
+
describe('onInit for course exercise', () => {
const programmingExercise = new ProgrammingExercise(new Course(), undefined);
programmingExercise.id = 123;
@@ -163,7 +149,6 @@ describe('ProgrammingExercise Management Detail Component', () => {
// THEN
expect(findWithTemplateAndSolutionParticipationStub).toHaveBeenCalledOnce();
- expect(gitDiffReportStub).toHaveBeenCalledOnce();
expect(statisticsServiceStub).toHaveBeenCalledOnce();
await Promise.resolve();
expect(comp.programmingExercise).toEqual(mockProgrammingExercise);
@@ -171,28 +156,7 @@ describe('ProgrammingExercise Management Detail Component', () => {
expect(comp.doughnutStats.participationsInPercent).toBe(100);
expect(comp.doughnutStats.resolvedPostsInPercent).toBe(50);
expect(comp.doughnutStats.absoluteAveragePoints).toBe(5);
- expect(comp.programmingExercise.gitDiffReport).toBeDefined();
- expect(comp.programmingExercise.gitDiffReport?.entries).toHaveLength(1);
});
-
- it.each([true, false])(
- 'should only call service method to get build log statistics onInit if the user is at least an editor for this exercise',
- async (isEditor: boolean) => {
- const programmingExercise = new ProgrammingExercise(new Course(), undefined);
- programmingExercise.id = 123;
- programmingExercise.isAtLeastEditor = isEditor;
- jest.spyOn(exerciseService, 'findWithTemplateAndSolutionParticipationAndLatestResults').mockReturnValue(
- of({ body: programmingExercise } as unknown as HttpResponse),
- );
- comp.ngOnInit();
- await new Promise((r) => setTimeout(r, 100));
- if (isEditor) {
- expect(buildLogStatisticsStub).toHaveBeenCalledOnce();
- } else {
- expect(buildLogStatisticsStub).not.toHaveBeenCalled();
- }
- },
- );
});
describe('onInit for exam exercise', () => {
@@ -217,12 +181,9 @@ describe('ProgrammingExercise Management Detail Component', () => {
await Promise.resolve();
expect(findWithTemplateAndSolutionParticipationStub).toHaveBeenCalledOnce();
expect(statisticsServiceStub).toHaveBeenCalledOnce();
- expect(gitDiffReportStub).toHaveBeenCalledOnce();
await Promise.resolve();
expect(comp.programmingExercise).toEqual(mockProgrammingExercise);
expect(comp.isExamExercise).toBeTrue();
- expect(comp.programmingExercise.gitDiffReport).toBeDefined();
- expect(comp.programmingExercise.gitDiffReport?.entries).toHaveLength(1);
}));
});
@@ -285,18 +246,6 @@ describe('ProgrammingExercise Management Detail Component', () => {
expect(comp.isBuildPlanEditable).toBe(editable);
});
- it('should reload on participation change', fakeAsync(() => {
- const loadDiffSpy = jest.spyOn(comp, 'loadGitDiffReport');
- jest.spyOn(exerciseService, 'getLatestResult').mockReturnValue({ successful: true });
- jest.spyOn(exerciseService, 'getLatestFullTestwiseCoverageReport').mockReturnValue(of({ coveredLineRatio: 0.5 }));
- comp.programmingExercise = mockProgrammingExercise;
- comp.programmingExercise.buildConfig!.testwiseCoverageEnabled = true;
- comp.onParticipationChange();
- tick();
- expect(loadDiffSpy).toHaveBeenCalledOnce();
- expect(comp.programmingExercise.coveredLinesRatio).toBe(0.5);
- }));
-
it('should combine template commit', () => {
const combineCommitsSpy = jest.spyOn(exerciseService, 'combineTemplateRepositoryCommits').mockReturnValue(of(new HttpResponse({ body: null })));
const successSpy = jest.spyOn(alertService, 'success');
diff --git a/src/test/javascript/spec/component/shared/image-cropper/exif.utils.spec.ts b/src/test/javascript/spec/component/shared/image-cropper/exif.utils.spec.ts
new file mode 100644
index 000000000000..fa0786442396
--- /dev/null
+++ b/src/test/javascript/spec/component/shared/image-cropper/exif.utils.spec.ts
@@ -0,0 +1,31 @@
+import { getTransformationsFromExifData } from 'app/shared/image-cropper/utils/exif.utils';
+import { ExifTransform } from 'app/shared/image-cropper/interfaces/exif-transform.interface';
+
+describe('ExifUtils', () => {
+ describe('getTransformationsFromExifData', () => {
+ it('should return correct transformations for given EXIF rotation values', () => {
+ const testCases: { input: number; expected: ExifTransform }[] = [
+ { input: 2, expected: { rotate: 0, flip: true } },
+ { input: 3, expected: { rotate: 2, flip: false } },
+ { input: 4, expected: { rotate: 2, flip: true } },
+ { input: 5, expected: { rotate: 1, flip: true } },
+ { input: 6, expected: { rotate: 1, flip: false } },
+ { input: 7, expected: { rotate: 3, flip: true } },
+ { input: 8, expected: { rotate: 3, flip: false } },
+ { input: 1, expected: { rotate: 0, flip: false } },
+ ];
+
+ testCases.forEach((testCase) => {
+ const result = getTransformationsFromExifData(testCase.input);
+ expect(result).toEqual(testCase.expected);
+ });
+ });
+
+ it('should return correct transformations for given base64 image string', () => {
+ const base64Image =
+ 'data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAAAAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/xABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==';
+ const result = getTransformationsFromExifData(base64Image);
+ expect(result).toEqual({ rotate: 1, flip: false });
+ });
+ });
+});
diff --git a/src/test/javascript/spec/component/shared/image-cropper/keyboard.utilts.spec.ts b/src/test/javascript/spec/component/shared/image-cropper/keyboard.utilts.spec.ts
new file mode 100644
index 000000000000..19b6070aef3d
--- /dev/null
+++ b/src/test/javascript/spec/component/shared/image-cropper/keyboard.utilts.spec.ts
@@ -0,0 +1,71 @@
+import { getEventForKey, getInvertedPositionForKey, getPositionForKey } from 'app/shared/image-cropper/utils/keyboard.utils';
+
+describe('Keyboard Utils', () => {
+ describe('getPositionForKey', () => {
+ it('should return correct position for ArrowUp', () => {
+ expect(getPositionForKey('ArrowUp')).toBe('top');
+ });
+
+ it('should return correct position for ArrowRight', () => {
+ expect(getPositionForKey('ArrowRight')).toBe('right');
+ });
+
+ it('should return correct position for ArrowDown', () => {
+ expect(getPositionForKey('ArrowDown')).toBe('bottom');
+ });
+
+ it('should return correct position for ArrowLeft', () => {
+ expect(getPositionForKey('ArrowLeft')).toBe('left');
+ });
+
+ it('should return default position for unknown key', () => {
+ expect(getPositionForKey('UnknownKey')).toBe('left');
+ });
+ });
+
+ describe('getInvertedPositionForKey', () => {
+ it('should return correct inverted position for ArrowUp', () => {
+ expect(getInvertedPositionForKey('ArrowUp')).toBe('bottom');
+ });
+
+ it('should return correct inverted position for ArrowRight', () => {
+ expect(getInvertedPositionForKey('ArrowRight')).toBe('left');
+ });
+
+ it('should return correct inverted position for ArrowDown', () => {
+ expect(getInvertedPositionForKey('ArrowDown')).toBe('top');
+ });
+
+ it('should return correct inverted position for ArrowLeft', () => {
+ expect(getInvertedPositionForKey('ArrowLeft')).toBe('right');
+ });
+
+ it('should return default inverted position for unknown key', () => {
+ expect(getInvertedPositionForKey('UnknownKey')).toBe('right');
+ });
+ });
+
+ describe('getEventForKey', () => {
+ const stepSize = 10;
+
+ it('should return correct event for ArrowUp', () => {
+ expect(getEventForKey('ArrowUp', stepSize)).toEqual({ clientX: 0, clientY: -stepSize });
+ });
+
+ it('should return correct event for ArrowRight', () => {
+ expect(getEventForKey('ArrowRight', stepSize)).toEqual({ clientX: stepSize, clientY: 0 });
+ });
+
+ it('should return correct event for ArrowDown', () => {
+ expect(getEventForKey('ArrowDown', stepSize)).toEqual({ clientX: 0, clientY: stepSize });
+ });
+
+ it('should return correct event for ArrowLeft', () => {
+ expect(getEventForKey('ArrowLeft', stepSize)).toEqual({ clientX: -stepSize, clientY: 0 });
+ });
+
+ it('should return default event for unknown key', () => {
+ expect(getEventForKey('UnknownKey', stepSize)).toEqual({ clientX: -stepSize, clientY: 0 });
+ });
+ });
+});
diff --git a/src/test/javascript/spec/util/shared/regex.util.spec.ts b/src/test/javascript/spec/util/shared/regex.util.spec.ts
new file mode 100644
index 000000000000..1501b95a9e4a
--- /dev/null
+++ b/src/test/javascript/spec/util/shared/regex.util.spec.ts
@@ -0,0 +1,34 @@
+import { matchesRegexFully } from 'app/utils/regex.util';
+
+describe('matchesRegexFully', () => {
+ it('should return true if regex is undefined', () => {
+ expect(matchesRegexFully('test', undefined)).toBeTrue();
+ });
+
+ it('should return false if input is undefined', () => {
+ expect(matchesRegexFully(undefined, 'test')).toBeFalse();
+ });
+
+ it('should return true for a full match', () => {
+ expect(matchesRegexFully('test', 'test')).toBeTrue();
+ });
+
+ it('should return false for a partial match', () => {
+ expect(matchesRegexFully('testing', 'test')).toBeFalse();
+ });
+
+ it('should return true for a match with regex special characters', () => {
+ expect(matchesRegexFully('test123', 'test\\d+')).toBeTrue();
+ });
+
+ it('should return false for no match', () => {
+ expect(matchesRegexFully('test', 'no-match')).toBeFalse();
+ });
+
+ it('should handle regex without ^ and $', () => {
+ expect(matchesRegexFully('test', 'test')).toBeTrue();
+ expect(matchesRegexFully('test', '^test')).toBeTrue();
+ expect(matchesRegexFully('test', 'test$')).toBeTrue();
+ expect(matchesRegexFully('test', '^test$')).toBeTrue();
+ });
+});