diff --git a/src/app/pages/waiting-room-to-car/cat-a-mod1/waiting-room-to-car.cat-a-mod1.page.html b/src/app/pages/waiting-room-to-car/cat-a-mod1/waiting-room-to-car.cat-a-mod1.page.html index 6cf24f556..55883c5b7 100644 --- a/src/app/pages/waiting-room-to-car/cat-a-mod1/waiting-room-to-car.cat-a-mod1.page.html +++ b/src/app/pages/waiting-room-to-car/cat-a-mod1/waiting-room-to-car.cat-a-mod1.page.html @@ -7,6 +7,7 @@ [category]="pageState.category$ | async" [isPracticeMode]="isPracticeMode" [shouldAuthenticate]="false" + (endTestLinkClicked)="abortMOTCall()" > @@ -47,6 +48,7 @@ [isPracticeMode]="isEndToEndPracticeMode" [vehicleRegistration]="pageState.registrationNumber$ | async" [isRekeyMode]="pageState.isRekeyMode$ | async" + [abortSubject]="abortSubject" > diff --git a/src/app/pages/waiting-room-to-car/cat-a-mod2/waiting-room-to-car.cat-a-mod2.page.html b/src/app/pages/waiting-room-to-car/cat-a-mod2/waiting-room-to-car.cat-a-mod2.page.html index 20c81a370..7c32e0f70 100644 --- a/src/app/pages/waiting-room-to-car/cat-a-mod2/waiting-room-to-car.cat-a-mod2.page.html +++ b/src/app/pages/waiting-room-to-car/cat-a-mod2/waiting-room-to-car.cat-a-mod2.page.html @@ -7,6 +7,7 @@ [category]="pageState.category$ | async" [isPracticeMode]="isPracticeMode" [shouldAuthenticate]="false" + (endTestLinkClicked)="abortMOTCall()" > @@ -67,6 +68,7 @@ [isPracticeMode]="isEndToEndPracticeMode" [vehicleRegistration]="pageState.registrationNumber$ | async" [isRekeyMode]="pageState.isRekeyMode$ | async" + [abortSubject]="abortSubject" > diff --git a/src/app/pages/waiting-room-to-car/cat-adi-part2/waiting-room-to-car.cat-adi-part2.page.html b/src/app/pages/waiting-room-to-car/cat-adi-part2/waiting-room-to-car.cat-adi-part2.page.html index 0664f8fee..68fb95392 100644 --- a/src/app/pages/waiting-room-to-car/cat-adi-part2/waiting-room-to-car.cat-adi-part2.page.html +++ b/src/app/pages/waiting-room-to-car/cat-adi-part2/waiting-room-to-car.cat-adi-part2.page.html @@ -10,6 +10,7 @@ [category]="pageState.category$ | async" [isPracticeMode]="isPracticeMode" [shouldAuthenticate]="false" + (endTestLinkClicked)="abortMOTCall()" > @@ -63,6 +64,7 @@ [isPracticeMode]="isEndToEndPracticeMode" [vehicleRegistration]="pageState.registrationNumber$ | async" [isRekeyMode]="pageState.isRekeyMode$ | async" + [abortSubject]="abortSubject" > diff --git a/src/app/pages/waiting-room-to-car/cat-adi-part3/waiting-room-to-car.cat-adi-part3.page.html b/src/app/pages/waiting-room-to-car/cat-adi-part3/waiting-room-to-car.cat-adi-part3.page.html index 5319e2d04..a730a87c5 100644 --- a/src/app/pages/waiting-room-to-car/cat-adi-part3/waiting-room-to-car.cat-adi-part3.page.html +++ b/src/app/pages/waiting-room-to-car/cat-adi-part3/waiting-room-to-car.cat-adi-part3.page.html @@ -10,6 +10,7 @@ [category]="pageState.category$ | async" [isPracticeMode]="isPracticeMode" [shouldAuthenticate]="false" + (endTestLinkClicked)="abortMOTCall()" > @@ -33,6 +34,7 @@ [isPracticeMode]="isEndToEndPracticeMode" [vehicleRegistration]="pageState.registrationNumber$ | async" [isRekeyMode]="pageState.isRekeyMode$ | async" + [abortSubject]="abortSubject" > diff --git a/src/app/pages/waiting-room-to-car/cat-b/waiting-room-to-car.cat-b.page.html b/src/app/pages/waiting-room-to-car/cat-b/waiting-room-to-car.cat-b.page.html index 5e9ee906b..4f21f71e4 100644 --- a/src/app/pages/waiting-room-to-car/cat-b/waiting-room-to-car.cat-b.page.html +++ b/src/app/pages/waiting-room-to-car/cat-b/waiting-room-to-car.cat-b.page.html @@ -12,6 +12,7 @@ [category]="pageState.category$ | async" [isPracticeMode]="isPracticeMode" [shouldAuthenticate]="false" + (endTestLinkClicked)="abortMOTCall()" > @@ -53,6 +54,7 @@ [isPracticeMode]="isEndToEndPracticeMode" [vehicleRegistration]="pageState.registrationNumber$ | async" [isRekeyMode]="pageState.isRekeyMode$ | async" + [abortSubject]="abortSubject" > diff --git a/src/app/pages/waiting-room-to-car/cat-c/waiting-room-to-car.cat-c.page.html b/src/app/pages/waiting-room-to-car/cat-c/waiting-room-to-car.cat-c.page.html index 1190fd55c..18a671d20 100644 --- a/src/app/pages/waiting-room-to-car/cat-c/waiting-room-to-car.cat-c.page.html +++ b/src/app/pages/waiting-room-to-car/cat-c/waiting-room-to-car.cat-c.page.html @@ -11,6 +11,7 @@ [isDelegated]="pageState.delegatedTest$ | async" [isPracticeMode]="isPracticeMode" [shouldAuthenticate]="false" + (endTestLinkClicked)="abortMOTCall()" > @@ -43,6 +44,7 @@ [isPracticeMode]="isEndToEndPracticeMode" [vehicleRegistration]="pageState.registrationNumber$ | async" [isRekeyMode]="pageState.isRekeyMode$ | async" + [abortSubject]="abortSubject" > diff --git a/src/app/pages/waiting-room-to-car/cat-cpc/waiting-room-to-car.cat-cpc.page.html b/src/app/pages/waiting-room-to-car/cat-cpc/waiting-room-to-car.cat-cpc.page.html index 6d307a03a..9116da909 100644 --- a/src/app/pages/waiting-room-to-car/cat-cpc/waiting-room-to-car.cat-cpc.page.html +++ b/src/app/pages/waiting-room-to-car/cat-cpc/waiting-room-to-car.cat-cpc.page.html @@ -11,6 +11,7 @@ [isDelegated]="pageState.delegatedTest$ | async" [isPracticeMode]="isPracticeMode" [shouldAuthenticate]="false" + (endTestLinkClicked)="abortMOTCall()" > @@ -43,6 +44,7 @@ [isPracticeMode]="isEndToEndPracticeMode" [vehicleRegistration]="pageState.registrationNumber$ | async" [isRekeyMode]="pageState.isRekeyMode$ | async" + [abortSubject]="abortSubject" > diff --git a/src/app/pages/waiting-room-to-car/cat-d/waiting-room-to-car.cat-d.page.html b/src/app/pages/waiting-room-to-car/cat-d/waiting-room-to-car.cat-d.page.html index 255eb722f..a22c4809b 100644 --- a/src/app/pages/waiting-room-to-car/cat-d/waiting-room-to-car.cat-d.page.html +++ b/src/app/pages/waiting-room-to-car/cat-d/waiting-room-to-car.cat-d.page.html @@ -11,6 +11,7 @@ [isDelegated]="pageState.delegatedTest$ | async" [isPracticeMode]="isPracticeMode" [shouldAuthenticate]="false" + (endTestLinkClicked)="abortMOTCall()" > @@ -43,6 +44,7 @@ [isPracticeMode]="isEndToEndPracticeMode" [vehicleRegistration]="pageState.registrationNumber$ | async" [isRekeyMode]="pageState.isRekeyMode$ | async" + [abortSubject]="abortSubject" > diff --git a/src/app/pages/waiting-room-to-car/cat-home-test/waiting-room-to-car.cat-home-test.page.html b/src/app/pages/waiting-room-to-car/cat-home-test/waiting-room-to-car.cat-home-test.page.html index 75e530628..890c8b7ed 100644 --- a/src/app/pages/waiting-room-to-car/cat-home-test/waiting-room-to-car.cat-home-test.page.html +++ b/src/app/pages/waiting-room-to-car/cat-home-test/waiting-room-to-car.cat-home-test.page.html @@ -10,6 +10,7 @@ [category]="pageState.category$ | async" [isPracticeMode]="isPracticeMode" [shouldAuthenticate]="false" + (endTestLinkClicked)="abortMOTCall()" > @@ -56,6 +57,7 @@ [isPracticeMode]="isEndToEndPracticeMode" [vehicleRegistration]="pageState.registrationNumber$ | async" [isRekeyMode]="pageState.isRekeyMode$ | async" + [abortSubject]="abortSubject" > diff --git a/src/app/pages/waiting-room-to-car/cat-manoeuvre/waiting-room-to-car.cat-manoeuvre.page.html b/src/app/pages/waiting-room-to-car/cat-manoeuvre/waiting-room-to-car.cat-manoeuvre.page.html index 33f988fda..7f6c380fe 100644 --- a/src/app/pages/waiting-room-to-car/cat-manoeuvre/waiting-room-to-car.cat-manoeuvre.page.html +++ b/src/app/pages/waiting-room-to-car/cat-manoeuvre/waiting-room-to-car.cat-manoeuvre.page.html @@ -11,6 +11,7 @@ [isDelegated]="pageState.delegatedTest$ | async" [isPracticeMode]="isPracticeMode" [shouldAuthenticate]="false" + (endTestLinkClicked)="abortMOTCall()" > @@ -34,6 +35,7 @@ [isPracticeMode]="isEndToEndPracticeMode" [vehicleRegistration]="pageState.registrationNumber$ | async" [isRekeyMode]="pageState.isRekeyMode$ | async" + [abortSubject]="abortSubject" > diff --git a/src/app/pages/waiting-room-to-car/components/vehicle-registration/__tests__/vehicle-registration.spec.ts b/src/app/pages/waiting-room-to-car/components/vehicle-registration/__tests__/vehicle-registration.spec.ts index 82336461f..f00ecfdcf 100644 --- a/src/app/pages/waiting-room-to-car/components/vehicle-registration/__tests__/vehicle-registration.spec.ts +++ b/src/app/pages/waiting-room-to-car/components/vehicle-registration/__tests__/vehicle-registration.spec.ts @@ -64,6 +64,14 @@ describe('VehicleRegistrationComponent', () => { }); }); + describe('abortMOTCall', () => { + it('should emit a value from abortSubject', () => { + spyOn(component.abortSubject, 'next'); + component.abortMOTCall(); + expect(component.abortSubject.next).toHaveBeenCalled(); + }); + }); + describe('invalid', () => { it('should return true if the formControl is invalid and dirty', () => { component.formControl.setValue(null); @@ -117,7 +125,7 @@ describe('VehicleRegistrationComponent', () => { }); describe('shouldDisableMOTButton', () => { it('should return true if the search spinner is shown', () => { - component.showSearchSpinner = true; + component.isSearchingForMOT = true; component.formControl.setValue('valid'); spyOn(component['networkState'], 'getNetworkState').and.returnValue(ConnectionStatus.ONLINE); @@ -125,7 +133,7 @@ describe('VehicleRegistrationComponent', () => { }); it('should return true if the form control is not valid', () => { - component.showSearchSpinner = false; + component.isSearchingForMOT = false; component.formControl.setValue(null); spyOn(component['networkState'], 'getNetworkState').and.returnValue(ConnectionStatus.ONLINE); @@ -133,7 +141,7 @@ describe('VehicleRegistrationComponent', () => { }); it('should return true if the network state is offline and not in practice mode', () => { - component.showSearchSpinner = false; + component.isSearchingForMOT = false; component.formControl.setValue('valid'); component.isPracticeMode = false; spyOn(component['networkState'], 'getNetworkState').and.returnValue(ConnectionStatus.OFFLINE); @@ -142,7 +150,7 @@ describe('VehicleRegistrationComponent', () => { }); it('should return false if the search spinner is not shown, form control is valid, and network state is online', () => { - component.showSearchSpinner = false; + component.isSearchingForMOT = false; component.formControl.setValue('valid'); spyOn(component['networkState'], 'getNetworkState').and.returnValue(ConnectionStatus.ONLINE); @@ -150,7 +158,7 @@ describe('VehicleRegistrationComponent', () => { }); it('should return false if the search spinner is not shown, form control is valid, and in practice mode', () => { - component.showSearchSpinner = false; + component.isSearchingForMOT = false; component.formControl.setValue('valid'); component.isPracticeMode = true; spyOn(component['networkState'], 'getNetworkState').and.returnValue(ConnectionStatus.OFFLINE); diff --git a/src/app/pages/waiting-room-to-car/components/vehicle-registration/vehicle-registration.html b/src/app/pages/waiting-room-to-car/components/vehicle-registration/vehicle-registration.html index 4e3256144..5e75762a7 100644 --- a/src/app/pages/waiting-room-to-car/components/vehicle-registration/vehicle-registration.html +++ b/src/app/pages/waiting-room-to-car/components/vehicle-registration/vehicle-registration.html @@ -39,7 +39,7 @@ - + diff --git a/src/app/pages/waiting-room-to-car/components/vehicle-registration/vehicle-registration.ts b/src/app/pages/waiting-room-to-car/components/vehicle-registration/vehicle-registration.ts index e692011b1..b3a3f0b21 100644 --- a/src/app/pages/waiting-room-to-car/components/vehicle-registration/vehicle-registration.ts +++ b/src/app/pages/waiting-room-to-car/components/vehicle-registration/vehicle-registration.ts @@ -17,6 +17,8 @@ import { nonAlphaNumericValues, } from '@shared/constants/field-validators/field-validators'; import { isEmpty } from 'lodash-es'; +import { Subject } from 'rxjs'; +import { finalize, takeUntil } from 'rxjs/operators'; @Component({ selector: 'vehicle-registration', @@ -32,6 +34,8 @@ export class VehicleRegistrationComponent implements OnChanges { isPracticeMode: boolean; @Input() isRekeyMode: boolean; + @Input() + abortSubject: Subject = new Subject(); @Output() vehicleRegistrationChange = new EventEmitter(); @@ -48,7 +52,7 @@ export class VehicleRegistrationComponent implements OnChanges { motData: MotHistoryWithStatus = null; modalData: string = null; hasCalledMOT = false; - showSearchSpinner = false; + isSearchingForMOT = false; readonly registrationNumberValidator: FieldValidators = getRegistrationNumberValidator(); @@ -73,36 +77,44 @@ export class VehicleRegistrationComponent implements OnChanges { async getMOT(value: string) { this.clearData(); this.hasCalledMOT = false; - this.showSearchSpinner = true; + this.isSearchingForMOT = true; if (!this.isPracticeMode) { const apiCall$ = this.motApiService.getMotHistoryByIdentifier(value); - apiCall$.subscribe(async (val) => { - // Assign the API response to the motData property - this.motData = val; - // Emit the vehicle registration number to update the search list - this.vrnSearchListUpdate.emit(value); - - // If the MOT status is not valid, open the reconfirm modal - if (this.motData?.data?.status === MotStatusCodes.NOT_VALID) { - await this.loadFailedMOTModal(); - // If the modal was cancelled, stop the spinner and return - if (this.modalData === ModalEvent.CANCEL) { - this.showSearchSpinner = false; - return; + apiCall$ + .pipe( + takeUntil(this.abortSubject), + finalize(() => { + // Stop the search spinner + this.isSearchingForMOT = false; + }) + ) + .subscribe(async (val) => { + // Assign the API response to the motData property + this.motData = val; + // Emit the vehicle registration number to update the search list + this.vrnSearchListUpdate.emit(value); + + // If the MOT status is not valid, open the reconfirm modal + if (this.motData?.data?.status === MotStatusCodes.NOT_VALID) { + await this.loadFailedMOTModal(); + // If the modal was cancelled, stop the spinner and return + if (this.modalData === ModalEvent.CANCEL) { + this.isSearchingForMOT = false; + return; + } + } + + // Set the flag indicating that the MOT call has been made + this.hasCalledMOT = true; + // Stop the search spinner + this.isSearchingForMOT = false; + // If motData is not null, emit the vehicle details + if (this.motData) { + this.motDetailsUpdate.emit(this.motData?.data); } - } - - // Set the flag indicating that the MOT call has been made - this.hasCalledMOT = true; - // Stop the search spinner - this.showSearchSpinner = false; - // If motData is not null, emit the vehicle details - if (this.motData) { - this.motDetailsUpdate.emit(this.motData?.data); - } - }); + }); } else { // Load the practice mode modal and wait for the user's response const fakeModalReturn = await this.loadPracticeModeModal(); @@ -110,24 +122,16 @@ export class VehicleRegistrationComponent implements OnChanges { // If the user indicates that the MOT failed, load the failed MOT modal if (fakeModalReturn === PracticeModeMOTType.FAILED) { await this.loadFailedMOTModal(); - // If the modal was cancelled, stop the spinner and return - if (this.modalData === ModalEvent.CANCEL) { - this.showSearchSpinner = false; - return; - } } // If the user cancelled the practice mode modal, reset motData and stop the spinner if (fakeModalReturn === ModalEvent.CANCEL) { this.motData = null; - this.showSearchSpinner = false; return; } // Set the flag indicating that the MOT call has been made this.hasCalledMOT = true; - // Stop the search spinner - this.showSearchSpinner = false; // Make a mock API call to get the MOT result based on the practice mode response this.motApiService @@ -179,6 +183,9 @@ export class VehicleRegistrationComponent implements OnChanges { vehicleRegistrationChanged(event: any): void { this.clearData(); + if (this.isSearchingForMOT) { + this.abortMOTCall(); + } this.hasCalledMOT = false; if (typeof event.target.value === 'string' && !this.registrationNumberValidator.pattern.test(event.target.value)) { event.target.value = event.target.value?.replace(nonAlphaNumericValues, ''); @@ -220,9 +227,19 @@ export class VehicleRegistrationComponent implements OnChanges { */ shouldDisableMOTButton(): boolean { return !( - !this.showSearchSpinner && + !this.isSearchingForMOT && this.formControl.valid && (this.networkState.getNetworkState() == ConnectionStatus.ONLINE || this.isPracticeMode) ); } + + /** + * Aborts the ongoing MOT call. + * + * This method emits a value from the `abortSubject`, + * which is used to signal the abortion of the ongoing HTTP request. + */ + abortMOTCall() { + this.abortSubject.next(); + } } diff --git a/src/app/shared/classes/test-flow-base-pages/waiting-room-to-car/__tests__/waiting-room-to-car-base-page.spec.ts b/src/app/shared/classes/test-flow-base-pages/waiting-room-to-car/__tests__/waiting-room-to-car-base-page.spec.ts index 809af2305..a773ee6db 100644 --- a/src/app/shared/classes/test-flow-base-pages/waiting-room-to-car/__tests__/waiting-room-to-car-base-page.spec.ts +++ b/src/app/shared/classes/test-flow-base-pages/waiting-room-to-car/__tests__/waiting-room-to-car-base-page.spec.ts @@ -380,4 +380,12 @@ describe('WaitingRoomToCarBasePageComponent', () => { ] as QuestionResult[]); }); }); + + describe('abortMOTCall', () => { + it('should emit a value from abortSubject', () => { + spyOn(basePageComponent.abortSubject, 'next'); + basePageComponent.abortMOTCall(); + expect(basePageComponent.abortSubject.next).toHaveBeenCalled(); + }); + }); }); diff --git a/src/app/shared/classes/test-flow-base-pages/waiting-room-to-car/waiting-room-to-car-base-page.ts b/src/app/shared/classes/test-flow-base-pages/waiting-room-to-car/waiting-room-to-car-base-page.ts index 6a6bd4cf4..b2e9bfb58 100644 --- a/src/app/shared/classes/test-flow-base-pages/waiting-room-to-car/waiting-room-to-car-base-page.ts +++ b/src/app/shared/classes/test-flow-base-pages/waiting-room-to-car/waiting-room-to-car-base-page.ts @@ -122,6 +122,7 @@ export abstract class WaitingRoomToCarBasePageComponent extends PracticeableBase testCategory: TestCategory; trainerNumberProvided = false; failedMOTModalCurrentlyOpen = false; + abortSubject: Subject = new Subject(); private categoriesRequiringEyesightTest: TestCategory[] = [ TestCategory.B, @@ -192,6 +193,7 @@ export abstract class WaitingRoomToCarBasePageComponent extends PracticeableBase } ionViewWillLeave(): void { + this.abortMOTCall(); this.store$.dispatch(PersistTests()); } @@ -331,4 +333,14 @@ export abstract class WaitingRoomToCarBasePageComponent extends PracticeableBase await alert.present(); } + + /** + * Aborts the ongoing MOT call. + * + * This method emits a value from the `abortSubject`, + * which is used to signal the abortion of the ongoing HTTP request. + */ + abortMOTCall() { + this.abortSubject.next(); + } } diff --git a/src/components/common/end-test-link/end-test-link.ts b/src/components/common/end-test-link/end-test-link.ts index f1d3444e5..fea4bbf46 100644 --- a/src/components/common/end-test-link/end-test-link.ts +++ b/src/components/common/end-test-link/end-test-link.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Router } from '@angular/router'; import { TerminateTestModal } from '@components/common/terminate-test-modal/terminate-test-modal'; import { TestCategory } from '@dvsa/mes-test-schema/category-definitions/common/test-category'; @@ -29,6 +29,9 @@ export class EndTestLinkComponent { @Input() isPracticeMode = false; + @Output() + endTestLinkClicked = new EventEmitter(); + constructor( public modalController: ModalController, public router: Router, @@ -37,6 +40,7 @@ export class EndTestLinkComponent { ) {} openEndTestModal = async (): Promise => { + this.endTestLinkClicked.emit(); this.terminateTestModal = await this.modalController.create({ id: 'TerminateTestModal', component: TerminateTestModal,