From 594d3fb867c621108d6b16d02030e0162af5fb10 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 12 Nov 2024 17:19:02 +0100 Subject: [PATCH] 120158: Emit response on modal closure when clicking outside the modal --- .../confirmation-modal.component.spec.ts | 24 ++++++-- .../confirmation-modal.component.ts | 18 +++++- ...export-metadata-selector.component.spec.ts | 9 +-- ...em-versions-delete-modal.component.spec.ts | 59 +++++++++++++++++-- .../item-versions-delete-modal.component.ts | 18 +++++- 5 files changed, 108 insertions(+), 20 deletions(-) diff --git a/src/app/shared/confirmation-modal/confirmation-modal.component.spec.ts b/src/app/shared/confirmation-modal/confirmation-modal.component.spec.ts index d899dd8ef86..3a26b264eda 100644 --- a/src/app/shared/confirmation-modal/confirmation-modal.component.spec.ts +++ b/src/app/shared/confirmation-modal/confirmation-modal.component.spec.ts @@ -1,4 +1,4 @@ -import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { DebugElement } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; @@ -13,15 +13,13 @@ describe('ConfirmationModalComponent', () => { const modalStub = jasmine.createSpyObj('modalStub', ['close']); beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ + void TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], declarations: [ConfirmationModalComponent], providers: [ { provide: NgbActiveModal, useValue: modalStub } ], - schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); - })); beforeEach(() => { @@ -31,8 +29,12 @@ describe('ConfirmationModalComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('should emit false on destroy when no button has been clicked', () => { + spyOn(component.response, 'emit'); + + component.ngOnDestroy(); + + expect(component.response.emit).toHaveBeenCalledOnceWith(false); }); describe('close', () => { @@ -55,6 +57,11 @@ describe('ConfirmationModalComponent', () => { it('behaviour subject should emit true', () => { expect(component.response.emit).toHaveBeenCalledWith(true); }); + it('should not emit again on destroy', () => { + component.ngOnDestroy(); + + expect(component.response.emit).toHaveBeenCalledTimes(1); + }); }); describe('cancelPressed', () => { @@ -68,6 +75,11 @@ describe('ConfirmationModalComponent', () => { it('behaviour subject should emit false', () => { expect(component.response.emit).toHaveBeenCalledWith(false); }); + it('should not emit again on destroy', () => { + component.ngOnDestroy(); + + expect(component.response.emit).toHaveBeenCalledTimes(1); + }); }); describe('when the click method emits on close button', () => { diff --git a/src/app/shared/confirmation-modal/confirmation-modal.component.ts b/src/app/shared/confirmation-modal/confirmation-modal.component.ts index 4fa48586007..4e5a8a6e23d 100644 --- a/src/app/shared/confirmation-modal/confirmation-modal.component.ts +++ b/src/app/shared/confirmation-modal/confirmation-modal.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { Component, EventEmitter, Input, Output, OnDestroy } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; @@ -6,7 +6,7 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model'; selector: 'ds-confirmation-modal', templateUrl: 'confirmation-modal.component.html', }) -export class ConfirmationModalComponent { +export class ConfirmationModalComponent implements OnDestroy { @Input() headerLabel: string; @Input() infoLabel: string; @Input() cancelLabel: string; @@ -25,13 +25,26 @@ export class ConfirmationModalComponent { @Output() response = new EventEmitter(); + /** + * Keep track whether one of the buttons was directly pressed. Used to emit the {@link response} when the user clicks + * outside the modal. + */ + buttonPressed = false; + constructor(protected activeModal: NgbActiveModal) { } + ngOnDestroy(): void { + if (!this.buttonPressed) { + this.response.emit(false); + } + } + /** * Confirm the action that led to the modal */ confirmPressed() { + this.buttonPressed = true; this.response.emit(true); this.close(); } @@ -40,6 +53,7 @@ export class ConfirmationModalComponent { * Cancel the action that led to the modal and close modal */ cancelPressed() { + this.buttonPressed = true; this.response.emit(false); this.close(); } diff --git a/src/app/shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component.spec.ts b/src/app/shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component.spec.ts index df3e4f095c3..9d3c98684fa 100644 --- a/src/app/shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component.spec.ts +++ b/src/app/shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component.spec.ts @@ -42,7 +42,6 @@ class ModelTestModule { describe('ExportMetadataSelectorComponent', () => { let component: ExportMetadataSelectorComponent; let fixture: ComponentFixture; - let debugElement: DebugElement; let modalRef; let router; @@ -132,17 +131,15 @@ describe('ExportMetadataSelectorComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ExportMetadataSelectorComponent); component = fixture.componentInstance; - debugElement = fixture.debugElement; const modalService = TestBed.inject(NgbModal); modalRef = modalService.open(ConfirmationModalComponent); modalRef.componentInstance.response = observableOf(true); + /* eslint-disable no-empty,@typescript-eslint/no-empty-function */ + modalRef.componentInstance.ngOnDestroy = () => {}; + /* eslint-enable no-empty,@typescript-eslint/no-empty-function */ fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); - describe('if item is selected', () => { let scriptRequestSucceeded; beforeEach((done) => { diff --git a/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.spec.ts b/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.spec.ts index 8a0d4a58d98..b459c94fdfb 100644 --- a/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.spec.ts +++ b/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.spec.ts @@ -9,12 +9,16 @@ describe('ItemVersionsDeleteModalComponent', () => { let component: ItemVersionsDeleteModalComponent; let fixture: ComponentFixture; + let modalStub: any; + beforeEach(async () => { + modalStub = jasmine.createSpyObj('modalStub', ['close', 'dismiss']); + await TestBed.configureTestingModule({ declarations: [ItemVersionsDeleteModalComponent], - imports: [ TranslateModule.forRoot(), RouterTestingModule.withRoutes([]) ], + imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], providers: [ - { provide: NgbActiveModal }, + { provide: NgbActiveModal, useValue: modalStub }, ] }).compileComponents(); }); @@ -25,7 +29,54 @@ describe('ItemVersionsDeleteModalComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); + + it('should emit false on destroy when no button has been clicked', () => { + spyOn(component.response, 'emit'); + + component.ngOnDestroy(); + + expect(component.response.emit).toHaveBeenCalledOnceWith(false); + }); + + describe('onModalClose', () => { + beforeEach(() => { + spyOn(component.response, 'emit'); + component.onModalClose(); + }); + + it('should call the close method on the active modal', () => { + expect(modalStub.dismiss).toHaveBeenCalled(); + }); + + it('behaviour subject should emit false', () => { + expect(component.response.emit).toHaveBeenCalledWith(false); + }); + + it('should not emit again on destroy', () => { + component.ngOnDestroy(); + + expect(component.response.emit).toHaveBeenCalledTimes(1); + }); + }); + + describe('onModalSubmit', () => { + beforeEach(() => { + spyOn(component.response, 'emit'); + component.onModalSubmit(); + }); + + it('should call the close method on the active modal', () => { + expect(modalStub.close).toHaveBeenCalled(); + }); + + it('behaviour subject should emit true', () => { + expect(component.response.emit).toHaveBeenCalledWith(true); + }); + + it('should not emit again on destroy', () => { + component.ngOnDestroy(); + + expect(component.response.emit).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.ts b/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.ts index 3aa1bbd49f3..dcf51e1ab43 100644 --- a/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.ts +++ b/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Output } from '@angular/core'; +import { Component, EventEmitter, Output, OnDestroy } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; @Component({ @@ -6,25 +6,39 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; templateUrl: './item-versions-delete-modal.component.html', styleUrls: ['./item-versions-delete-modal.component.scss'] }) -export class ItemVersionsDeleteModalComponent { +export class ItemVersionsDeleteModalComponent implements OnDestroy { /** * An event fired when the cancel or confirm button is clicked, with respectively false or true */ @Output() response = new EventEmitter(); + /** + * Keep track whether one of the buttons was directly pressed. Used to emit the {@link response} when the user clicks + * outside the modal. + */ + buttonPressed = false; + versionNumber: number; constructor( protected activeModal: NgbActiveModal,) { } + ngOnDestroy(): void { + if (!this.buttonPressed) { + this.response.emit(false); + } + } + onModalClose() { + this.buttonPressed = true; this.response.emit(false); this.activeModal.dismiss(); } onModalSubmit() { + this.buttonPressed = true; this.response.emit(true); this.activeModal.close(); }