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(); + }); +});