import { RbdService } from '../../../shared/api/rbd.service';
import { ConfirmationModalComponent } from '../../../shared/components/confirmation-modal/confirmation-modal.component';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
import { TableComponent } from '../../../shared/datatable/table/table.component';
import { CellTemplate } from '../../../shared/enum/cell-template.enum';
import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum';
const poolName = this.selection.first().pool_name;
const imageName = this.selection.first().name;
- this.modalRef = this.modalService.show(DeletionModalComponent, {
+ this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
initialState: {
itemDescription: 'RBD',
submitActionObservable: () =>
import { RbdService } from '../../../shared/api/rbd.service';
import { ConfirmationModalComponent } from '../../../shared/components/confirmation-modal/confirmation-modal.component';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
import { CellTemplate } from '../../../shared/enum/cell-template.enum';
import { CdTableAction } from '../../../shared/models/cd-table-action';
import { CdTableColumn } from '../../../shared/models/cd-table-column';
deleteSnapshotModal() {
const snapshotName = this.selection.selected[0].name;
- this.modalRef = this.modalService.show(DeletionModalComponent, {
+ this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
initialState: {
itemDescription: 'RBD snapshot',
submitAction: () => this._asyncTask('deleteSnapshot', 'rbd/snap/delete', snapshotName)
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { RbdService } from '../../../shared/api/rbd.service';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
import { TableComponent } from '../../../shared/datatable/table/table.component';
import { CellTemplate } from '../../../shared/enum/cell-template.enum';
import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum';
const imageId = this.selection.first().id;
const expiresAt = this.selection.first().deferment_end_time;
- this.modalRef = this.modalService.show(DeletionModalComponent, {
+ this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
initialState: {
itemDescription: 'RBD',
bodyTemplate: this.deleteTpl,
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { OsdService } from '../../../../shared/api/osd.service';
+import { ConfirmationModalComponent } from '../../../../shared/components/confirmation-modal/confirmation-modal.component';
import { TableComponent } from '../../../../shared/datatable/table/table.component';
import { CellTemplate } from '../../../../shared/enum/cell-template.enum';
import { CdTableAction } from '../../../../shared/models/cd-table-action';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { PoolService } from '../../../shared/api/pool.service';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
import { TableComponent } from '../../../shared/datatable/table/table.component';
import { CellTemplate } from '../../../shared/enum/cell-template.enum';
import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum';
import { TaskListService } from '../../../shared/services/task-list.service';
import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
import { Pool } from '../pool';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
@Component({
selector: 'cd-pool-list',
deletePoolModal() {
const name = this.selection.first().pool_name;
- this.modalRef = this.modalService.show(DeletionModalComponent, {
+ this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
initialState: {
itemDescription: 'Pool',
submitActionObservable: () =>
import { forkJoin as observableForkJoin, Observable, Subscriber } from 'rxjs';
import { RgwBucketService } from '../../../shared/api/rgw-bucket.service';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
import { TableComponent } from '../../../shared/datatable/table/table.component';
import { CdTableAction } from '../../../shared/models/cd-table-action';
import { CdTableColumn } from '../../../shared/models/cd-table-column';
}
deleteAction() {
- this.bsModalService.show(DeletionModalComponent, {
+ this.bsModalService.show(CriticalConfirmationModalComponent, {
initialState: {
itemDescription: this.selection.hasSingleSelection ? 'bucket' : 'buckets',
submitActionObservable: () => {
import { forkJoin as observableForkJoin, Observable, Subscriber } from 'rxjs';
import { RgwUserService } from '../../../shared/api/rgw-user.service';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
import { TableComponent } from '../../../shared/datatable/table/table.component';
import { CellTemplate } from '../../../shared/enum/cell-template.enum';
import { CdTableAction } from '../../../shared/models/cd-table-action';
}
deleteAction() {
- this.bsModalService.show(DeletionModalComponent, {
+ this.bsModalService.show(CriticalConfirmationModalComponent, {
initialState: {
itemDescription: this.selection.hasSingleSelection ? 'user' : 'users',
submitActionObservable: (): Observable<any> => {
import { RoleService } from '../../../shared/api/role.service';
import { ScopeService } from '../../../shared/api/scope.service';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
import { EmptyPipe } from '../../../shared/empty.pipe';
import { CellTemplate } from '../../../shared/enum/cell-template.enum';
import { NotificationType } from '../../../shared/enum/notification-type.enum';
deleteRoleModal() {
const name = this.selection.first().name;
- this.modalRef = this.modalService.show(DeletionModalComponent, {
+ this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
initialState: {
itemDescription: 'Role',
submitAction: () => this.deleteRole(name)
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { UserService } from '../../../shared/api/user.service';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
import { EmptyPipe } from '../../../shared/empty.pipe';
import { NotificationType } from '../../../shared/enum/notification-type.enum';
import { CdTableAction } from '../../../shared/models/cd-table-action';
);
return;
}
- this.modalRef = this.modalService.show(DeletionModalComponent, {
+ this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
initialState: {
itemDescription: 'User',
submitAction: () => this.deleteUser(username)
import { DirectivesModule } from '../directives/directives.module';
import { PipesModule } from '../pipes/pipes.module';
import { ConfirmationModalComponent } from './confirmation-modal/confirmation-modal.component';
-import { DeletionModalComponent } from './deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from './critical-confirmation-modal/critical-confirmation-modal.component';
import { ErrorPanelComponent } from './error-panel/error-panel.component';
import { GrafanaComponent } from './grafana/grafana.component';
import { HelperComponent } from './helper/helper.component';
LoadingPanelComponent,
InfoPanelComponent,
ModalComponent,
- DeletionModalComponent,
+ CriticalConfirmationModalComponent,
ConfirmationModalComponent,
WarningPanelComponent,
GrafanaComponent
WarningPanelComponent,
GrafanaComponent
],
- entryComponents: [ModalComponent, DeletionModalComponent, ConfirmationModalComponent]
+ entryComponents: [
+ ModalComponent,
+ CriticalConfirmationModalComponent,
+ ConfirmationModalComponent
+ ]
})
export class ComponentsModule {}
--- /dev/null
+<cd-modal #modal
+ [modalRef]="modalRef">
+ <ng-container class="modal-title">
+ <ng-container *ngTemplateOutlet="deletionHeading"></ng-container>
+ </ng-container>
+
+ <ng-container class="modal-content">
+ <form name="deletionForm"
+ #formDir="ngForm"
+ [formGroup]="deletionForm"
+ novalidate>
+ <div class="modal-body">
+ <ng-container *ngTemplateOutlet="bodyTemplate; context: bodyContext"></ng-container>
+ <div class="question">
+ <p i18n>
+ Are you sure that you want to {{ actionDescription | lowercase }} the selected {{ itemDescription }}?
+ </p>
+ <div class="form-group"
+ [ngClass]="{'has-error': deletionForm.showError('confirmation', formDir)}">
+ <div class="checkbox checkbox-primary">
+ <input type="checkbox"
+ name="confirmation"
+ id="confirmation"
+ formControlName="confirmation"
+ autofocus>
+ <label i18n
+ for="confirmation">
+ Yes, I am sure.
+ </label>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <cd-submit-button #submitButton
+ [form]="deletionForm"
+ (submitAction)="callSubmitAction()">
+ <ng-container *ngTemplateOutlet="deletionHeading"></ng-container>
+ </cd-submit-button>
+ <button class="btn btn-link btn-sm"
+ (click)="hideModal()"
+ i18n>
+ Cancel
+ </button>
+ </div>
+ </form>
+ </ng-container>
+</cd-modal>
+
+<ng-template #deletionHeading>
+ <ng-container i18n>
+ {{ actionDescription | titlecase }} {{ itemDescription }}
+ </ng-container>
+</ng-template>
--- /dev/null
+.modal-body .question {
+ font-weight: bold;
+}
+.modal-body .question .checkbox {
+ padding-top: 7px;
+}
--- /dev/null
+import { Component, NgModule, NO_ERRORS_SCHEMA, TemplateRef, ViewChild } from '@angular/core';
+import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { NgForm, ReactiveFormsModule } from '@angular/forms';
+import { By } from '@angular/platform-browser';
+
+import { BsModalRef, BsModalService, ModalModule } from 'ngx-bootstrap/modal';
+import { Observable, Subscriber, timer as observableTimer } from 'rxjs';
+
+import { configureTestBed } from '../../../../testing/unit-test-helper';
+import { DirectivesModule } from '../../directives/directives.module';
+import { CriticalConfirmationModalComponent } from './critical-confirmation-modal.component';
+
+@NgModule({
+ entryComponents: [CriticalConfirmationModalComponent]
+})
+export class MockModule {}
+
+@Component({
+ template: `
+ <button type="button"
+ class="btn btn-sm btn-primary"
+ (click)="openCtrlDriven()">
+ <i class="fa fa-fw fa-times"></i>Deletion Ctrl-Test
+ <ng-template #ctrlDescription>
+ The spinner is handled by the controller if you have use the modal as ViewChild in order to
+ use it's functions to stop the spinner or close the dialog.
+ </ng-template>
+ </button>
+
+ <button type="button"
+ class="btn btn-sm btn-primary"
+ (click)="openModalDriven()">
+ <i class="fa fa-fw fa-times"></i>Deletion Modal-Test
+ <ng-template #modalDescription>
+ The spinner is handled by the modal if your given deletion function returns a Observable.
+ </ng-template>
+ </button>
+ `
+})
+class MockComponent {
+ @ViewChild('ctrlDescription')
+ ctrlDescription: TemplateRef<any>;
+ @ViewChild('modalDescription')
+ modalDescription: TemplateRef<any>;
+ someData = [1, 2, 3, 4, 5];
+ finished: number[];
+ ctrlRef: BsModalRef;
+ modalRef: BsModalRef;
+
+ // Normally private - public was needed for the tests
+ constructor(public modalService: BsModalService) {}
+
+ openCtrlDriven() {
+ this.ctrlRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ initialState: {
+ submitAction: this.fakeDeleteController.bind(this),
+ bodyTemplate: this.ctrlDescription
+ }
+ });
+ }
+
+ openModalDriven() {
+ this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ initialState: {
+ submitActionObservable: this.fakeDelete(),
+ bodyTemplate: this.modalDescription
+ }
+ });
+ }
+
+ finish() {
+ this.finished = [6, 7, 8, 9];
+ }
+
+ fakeDelete() {
+ return (): Observable<any> => {
+ return new Observable((observer: Subscriber<any>) => {
+ observableTimer(100).subscribe(() => {
+ observer.next(this.finish());
+ observer.complete();
+ });
+ });
+ };
+ }
+
+ fakeDeleteController() {
+ observableTimer(100).subscribe(() => {
+ this.finish();
+ this.ctrlRef.hide();
+ });
+ }
+}
+
+describe('DeletionModalComponent', () => {
+ let mockComponent: MockComponent;
+ let component: CriticalConfirmationModalComponent;
+ let mockFixture: ComponentFixture<MockComponent>;
+ let fixture: ComponentFixture<CriticalConfirmationModalComponent>;
+
+ configureTestBed({
+ declarations: [MockComponent, CriticalConfirmationModalComponent],
+ schemas: [NO_ERRORS_SCHEMA],
+ imports: [ModalModule.forRoot(), ReactiveFormsModule, MockModule, DirectivesModule],
+ providers: [BsModalRef]
+ });
+
+ beforeEach(() => {
+ mockFixture = TestBed.createComponent(MockComponent);
+ mockComponent = mockFixture.componentInstance;
+ // Mocking the modals as a lot would be left over
+ spyOn(mockComponent.modalService, 'show').and.callFake((modalComp, config) => {
+ const ref = new BsModalRef();
+ fixture = TestBed.createComponent(CriticalConfirmationModalComponent);
+ component = fixture.componentInstance;
+ if (config.initialState) {
+ component = Object.assign(component, config.initialState);
+ }
+ fixture.detectChanges();
+ ref.content = component;
+ return ref;
+ });
+ mockComponent.openCtrlDriven();
+ mockFixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should focus the checkbox form field', () => {
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ const focused = fixture.debugElement.query(By.css(':focus'));
+ expect(focused.attributes.id).toBe('confirmation');
+ expect(focused.attributes.type).toBe('checkbox');
+ const element = document.getElementById('confirmation');
+ expect(element === document.activeElement).toBeTruthy();
+ });
+ });
+
+ it('should throw an error if no action is defined', () => {
+ component = Object.assign(component, {
+ submitAction: null,
+ submitActionObservable: null
+ });
+ expect(() => component.ngOnInit()).toThrowError('No submit action defined');
+ });
+
+ it('should test if the ctrl driven mock is set correctly through mock component', () => {
+ expect(component.bodyTemplate).toBeTruthy();
+ expect(component.submitAction).toBeTruthy();
+ expect(component.submitActionObservable).not.toBeTruthy();
+ });
+
+ it('should test if the modal driven mock is set correctly through mock component', () => {
+ mockComponent.openModalDriven();
+ expect(component.bodyTemplate).toBeTruthy();
+ expect(component.submitActionObservable).toBeTruthy();
+ expect(component.submitAction).not.toBeTruthy();
+ });
+
+ describe('component functions', () => {
+ const changeValue = (value) => {
+ const ctrl = component.deletionForm.get('confirmation');
+ ctrl.setValue(value);
+ ctrl.markAsDirty();
+ ctrl.updateValueAndValidity();
+ fixture.detectChanges();
+ };
+
+ it('should test hideModal', () => {
+ expect(component.modalRef).toBeTruthy();
+ expect(component.hideModal).toBeTruthy();
+ spyOn(component.modalRef, 'hide').and.callThrough();
+ expect(component.modalRef.hide).not.toHaveBeenCalled();
+ component.hideModal();
+ expect(component.modalRef.hide).toHaveBeenCalled();
+ });
+
+ describe('validate confirmation', () => {
+ const testValidation = (submitted: boolean, error: string, expected: boolean) => {
+ expect(
+ component.deletionForm.showError('confirmation', <NgForm>{ submitted: submitted }, error)
+ ).toBe(expected);
+ };
+
+ beforeEach(() => {
+ component.deletionForm.reset();
+ });
+
+ it('should test empty values', () => {
+ component.deletionForm.reset();
+ testValidation(false, undefined, false);
+ testValidation(true, 'required', true);
+ component.deletionForm.reset();
+ changeValue(true);
+ changeValue(false);
+ testValidation(true, 'required', true);
+ });
+ });
+
+ describe('deletion call', () => {
+ beforeEach(() => {
+ spyOn(component, 'stopLoadingSpinner').and.callThrough();
+ spyOn(component, 'hideModal').and.callThrough();
+ });
+
+ describe('Controller driven', () => {
+ beforeEach(() => {
+ spyOn(component, 'submitAction').and.callThrough();
+ spyOn(mockComponent.ctrlRef, 'hide').and.callThrough();
+ });
+
+ it('should test fake deletion that closes modal', <any>fakeAsync(() => {
+ // Before deletionCall
+ expect(component.submitAction).not.toHaveBeenCalled();
+ // During deletionCall
+ component.callSubmitAction();
+ expect(component.stopLoadingSpinner).not.toHaveBeenCalled();
+ expect(component.hideModal).not.toHaveBeenCalled();
+ expect(mockComponent.ctrlRef.hide).not.toHaveBeenCalled();
+ expect(component.submitAction).toHaveBeenCalled();
+ expect(mockComponent.finished).toBe(undefined);
+ // After deletionCall
+ tick(2000);
+ expect(component.hideModal).not.toHaveBeenCalled();
+ expect(mockComponent.ctrlRef.hide).toHaveBeenCalled();
+ expect(mockComponent.finished).toEqual([6, 7, 8, 9]);
+ }));
+ });
+
+ describe('Modal driven', () => {
+ beforeEach(() => {
+ mockComponent.openModalDriven();
+ spyOn(component, 'stopLoadingSpinner').and.callThrough();
+ spyOn(component, 'hideModal').and.callThrough();
+ spyOn(mockComponent, 'fakeDelete').and.callThrough();
+ });
+
+ it('should delete and close modal', <any>fakeAsync(() => {
+ // During deletionCall
+ component.callSubmitAction();
+ expect(mockComponent.finished).toBe(undefined);
+ expect(component.hideModal).not.toHaveBeenCalled();
+ // After deletionCall
+ tick(2000);
+ expect(mockComponent.finished).toEqual([6, 7, 8, 9]);
+ expect(component.stopLoadingSpinner).not.toHaveBeenCalled();
+ expect(component.hideModal).toHaveBeenCalled();
+ }));
+ });
+ });
+ });
+});
--- /dev/null
+import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
+import { FormControl, Validators } from '@angular/forms';
+
+import { BsModalRef } from 'ngx-bootstrap/modal';
+import { Observable } from 'rxjs';
+
+import { CdFormGroup } from '../../forms/cd-form-group';
+import { SubmitButtonComponent } from '../submit-button/submit-button.component';
+
+@Component({
+ selector: 'cd-deletion-modal',
+ templateUrl: './critical-confirmation-modal.component.html',
+ styleUrls: ['./critical-confirmation-modal.component.scss']
+})
+export class CriticalConfirmationModalComponent implements OnInit {
+ @ViewChild(SubmitButtonComponent)
+ submitButton: SubmitButtonComponent;
+ bodyTemplate: TemplateRef<any>;
+ bodyContext: any;
+ submitActionObservable: () => Observable<any>;
+ submitAction: Function;
+ deletionForm: CdFormGroup;
+ itemDescription: 'entry';
+ actionDescription = 'delete';
+
+ constructor(public modalRef: BsModalRef) {}
+
+ ngOnInit() {
+ this.deletionForm = new CdFormGroup({
+ confirmation: new FormControl(false, [Validators.requiredTrue])
+ });
+
+ if (!(this.submitAction || this.submitActionObservable)) {
+ throw new Error('No submit action defined');
+ }
+ }
+
+ callSubmitAction() {
+ if (this.submitActionObservable) {
+ this.submitActionObservable().subscribe(
+ null,
+ this.stopLoadingSpinner.bind(this),
+ this.hideModal.bind(this)
+ );
+ } else {
+ this.submitAction();
+ }
+ }
+
+ hideModal() {
+ this.modalRef.hide();
+ }
+
+ stopLoadingSpinner() {
+ this.deletionForm.setErrors({ cdSubmitButton: true });
+ }
+}
+++ /dev/null
-<cd-modal #modal
- [modalRef]="modalRef">
- <ng-container class="modal-title">
- <ng-container *ngTemplateOutlet="deletionHeading"></ng-container>
- </ng-container>
-
- <ng-container class="modal-content">
- <form name="deletionForm"
- #formDir="ngForm"
- [formGroup]="deletionForm"
- novalidate>
- <div class="modal-body">
- <ng-container *ngTemplateOutlet="bodyTemplate; context: bodyContext"></ng-container>
- <div class="question">
- <p i18n>
- Are you sure that you want to {{ actionDescription | lowercase }} the selected {{ itemDescription }}?
- </p>
- <div class="form-group"
- [ngClass]="{'has-error': deletionForm.showError('confirmation', formDir)}">
- <div class="checkbox checkbox-primary">
- <input type="checkbox"
- name="confirmation"
- id="confirmation"
- formControlName="confirmation"
- autofocus>
- <label i18n
- for="confirmation">
- Yes, I am sure.
- </label>
- </div>
- </div>
- </div>
- </div>
- <div class="modal-footer">
- <cd-submit-button #submitButton
- [form]="deletionForm"
- (submitAction)="callSubmitAction()">
- <ng-container *ngTemplateOutlet="deletionHeading"></ng-container>
- </cd-submit-button>
- <button class="btn btn-link btn-sm"
- (click)="hideModal()"
- i18n>
- Cancel
- </button>
- </div>
- </form>
- </ng-container>
-</cd-modal>
-
-<ng-template #deletionHeading>
- <ng-container i18n>
- {{ actionDescription | titlecase }} {{ itemDescription }}
- </ng-container>
-</ng-template>
+++ /dev/null
-.modal-body .question {
- font-weight: bold;
-}
-.modal-body .question .checkbox {
- padding-top: 7px;
-}
+++ /dev/null
-import { Component, NgModule, NO_ERRORS_SCHEMA, TemplateRef, ViewChild } from '@angular/core';
-import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
-import { NgForm, ReactiveFormsModule } from '@angular/forms';
-import { By } from '@angular/platform-browser';
-
-import { BsModalRef, BsModalService, ModalModule } from 'ngx-bootstrap/modal';
-import { Observable, Subscriber, timer as observableTimer } from 'rxjs';
-
-import { configureTestBed } from '../../../../testing/unit-test-helper';
-import { DirectivesModule } from '../../directives/directives.module';
-import { DeletionModalComponent } from './deletion-modal.component';
-
-@NgModule({
- entryComponents: [DeletionModalComponent]
-})
-export class MockModule {}
-
-@Component({
- template: `
- <button type="button"
- class="btn btn-sm btn-primary"
- (click)="openCtrlDriven()">
- <i class="fa fa-fw fa-times"></i>Deletion Ctrl-Test
- <ng-template #ctrlDescription>
- The spinner is handled by the controller if you have use the modal as ViewChild in order to
- use it's functions to stop the spinner or close the dialog.
- </ng-template>
- </button>
-
- <button type="button"
- class="btn btn-sm btn-primary"
- (click)="openModalDriven()">
- <i class="fa fa-fw fa-times"></i>Deletion Modal-Test
- <ng-template #modalDescription>
- The spinner is handled by the modal if your given deletion function returns a Observable.
- </ng-template>
- </button>
- `
-})
-class MockComponent {
- @ViewChild('ctrlDescription')
- ctrlDescription: TemplateRef<any>;
- @ViewChild('modalDescription')
- modalDescription: TemplateRef<any>;
- someData = [1, 2, 3, 4, 5];
- finished: number[];
- ctrlRef: BsModalRef;
- modalRef: BsModalRef;
-
- // Normally private - public was needed for the tests
- constructor(public modalService: BsModalService) {}
-
- openCtrlDriven() {
- this.ctrlRef = this.modalService.show(DeletionModalComponent, {
- initialState: {
- submitAction: this.fakeDeleteController.bind(this),
- bodyTemplate: this.ctrlDescription
- }
- });
- }
-
- openModalDriven() {
- this.modalRef = this.modalService.show(DeletionModalComponent, {
- initialState: {
- submitActionObservable: this.fakeDelete(),
- bodyTemplate: this.modalDescription
- }
- });
- }
-
- finish() {
- this.finished = [6, 7, 8, 9];
- }
-
- fakeDelete() {
- return (): Observable<any> => {
- return new Observable((observer: Subscriber<any>) => {
- observableTimer(100).subscribe(() => {
- observer.next(this.finish());
- observer.complete();
- });
- });
- };
- }
-
- fakeDeleteController() {
- observableTimer(100).subscribe(() => {
- this.finish();
- this.ctrlRef.hide();
- });
- }
-}
-
-describe('DeletionModalComponent', () => {
- let mockComponent: MockComponent;
- let component: DeletionModalComponent;
- let mockFixture: ComponentFixture<MockComponent>;
- let fixture: ComponentFixture<DeletionModalComponent>;
-
- configureTestBed({
- declarations: [MockComponent, DeletionModalComponent],
- schemas: [NO_ERRORS_SCHEMA],
- imports: [ModalModule.forRoot(), ReactiveFormsModule, MockModule, DirectivesModule],
- providers: [BsModalRef]
- });
-
- beforeEach(() => {
- mockFixture = TestBed.createComponent(MockComponent);
- mockComponent = mockFixture.componentInstance;
- // Mocking the modals as a lot would be left over
- spyOn(mockComponent.modalService, 'show').and.callFake((modalComp, config) => {
- const ref = new BsModalRef();
- fixture = TestBed.createComponent(DeletionModalComponent);
- component = fixture.componentInstance;
- if (config.initialState) {
- component = Object.assign(component, config.initialState);
- }
- fixture.detectChanges();
- ref.content = component;
- return ref;
- });
- mockComponent.openCtrlDriven();
- mockFixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-
- it('should focus the checkbox form field', () => {
- fixture.detectChanges();
- fixture.whenStable().then(() => {
- const focused = fixture.debugElement.query(By.css(':focus'));
- expect(focused.attributes.id).toBe('confirmation');
- expect(focused.attributes.type).toBe('checkbox');
- const element = document.getElementById('confirmation');
- expect(element === document.activeElement).toBeTruthy();
- });
- });
-
- it('should throw an error if no action is defined', () => {
- component = Object.assign(component, {
- submitAction: null,
- submitActionObservable: null
- });
- expect(() => component.ngOnInit()).toThrowError('No submit action defined');
- });
-
- it('should test if the ctrl driven mock is set correctly through mock component', () => {
- expect(component.bodyTemplate).toBeTruthy();
- expect(component.submitAction).toBeTruthy();
- expect(component.submitActionObservable).not.toBeTruthy();
- });
-
- it('should test if the modal driven mock is set correctly through mock component', () => {
- mockComponent.openModalDriven();
- expect(component.bodyTemplate).toBeTruthy();
- expect(component.submitActionObservable).toBeTruthy();
- expect(component.submitAction).not.toBeTruthy();
- });
-
- describe('component functions', () => {
- const changeValue = (value) => {
- const ctrl = component.deletionForm.get('confirmation');
- ctrl.setValue(value);
- ctrl.markAsDirty();
- ctrl.updateValueAndValidity();
- fixture.detectChanges();
- };
-
- it('should test hideModal', () => {
- expect(component.modalRef).toBeTruthy();
- expect(component.hideModal).toBeTruthy();
- spyOn(component.modalRef, 'hide').and.callThrough();
- expect(component.modalRef.hide).not.toHaveBeenCalled();
- component.hideModal();
- expect(component.modalRef.hide).toHaveBeenCalled();
- });
-
- describe('validate confirmation', () => {
- const testValidation = (submitted: boolean, error: string, expected: boolean) => {
- expect(
- component.deletionForm.showError('confirmation', <NgForm>{ submitted: submitted }, error)
- ).toBe(expected);
- };
-
- beforeEach(() => {
- component.deletionForm.reset();
- });
-
- it('should test empty values', () => {
- component.deletionForm.reset();
- testValidation(false, undefined, false);
- testValidation(true, 'required', true);
- component.deletionForm.reset();
- changeValue(true);
- changeValue(false);
- testValidation(true, 'required', true);
- });
- });
-
- describe('deletion call', () => {
- beforeEach(() => {
- spyOn(component, 'stopLoadingSpinner').and.callThrough();
- spyOn(component, 'hideModal').and.callThrough();
- });
-
- describe('Controller driven', () => {
- beforeEach(() => {
- spyOn(component, 'submitAction').and.callThrough();
- spyOn(mockComponent.ctrlRef, 'hide').and.callThrough();
- });
-
- it('should test fake deletion that closes modal', <any>fakeAsync(() => {
- // Before deletionCall
- expect(component.submitAction).not.toHaveBeenCalled();
- // During deletionCall
- component.callSubmitAction();
- expect(component.stopLoadingSpinner).not.toHaveBeenCalled();
- expect(component.hideModal).not.toHaveBeenCalled();
- expect(mockComponent.ctrlRef.hide).not.toHaveBeenCalled();
- expect(component.submitAction).toHaveBeenCalled();
- expect(mockComponent.finished).toBe(undefined);
- // After deletionCall
- tick(2000);
- expect(component.hideModal).not.toHaveBeenCalled();
- expect(mockComponent.ctrlRef.hide).toHaveBeenCalled();
- expect(mockComponent.finished).toEqual([6, 7, 8, 9]);
- }));
- });
-
- describe('Modal driven', () => {
- beforeEach(() => {
- mockComponent.openModalDriven();
- spyOn(component, 'stopLoadingSpinner').and.callThrough();
- spyOn(component, 'hideModal').and.callThrough();
- spyOn(mockComponent, 'fakeDelete').and.callThrough();
- });
-
- it('should delete and close modal', <any>fakeAsync(() => {
- // During deletionCall
- component.callSubmitAction();
- expect(mockComponent.finished).toBe(undefined);
- expect(component.hideModal).not.toHaveBeenCalled();
- // After deletionCall
- tick(2000);
- expect(mockComponent.finished).toEqual([6, 7, 8, 9]);
- expect(component.stopLoadingSpinner).not.toHaveBeenCalled();
- expect(component.hideModal).toHaveBeenCalled();
- }));
- });
- });
- });
-});
+++ /dev/null
-import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
-import { FormControl, Validators } from '@angular/forms';
-
-import { BsModalRef } from 'ngx-bootstrap/modal';
-import { Observable } from 'rxjs';
-
-import { CdFormGroup } from '../../forms/cd-form-group';
-import { SubmitButtonComponent } from '../submit-button/submit-button.component';
-
-@Component({
- selector: 'cd-deletion-modal',
- templateUrl: './deletion-modal.component.html',
- styleUrls: ['./deletion-modal.component.scss']
-})
-export class DeletionModalComponent implements OnInit {
- @ViewChild(SubmitButtonComponent)
- submitButton: SubmitButtonComponent;
- bodyTemplate: TemplateRef<any>;
- bodyContext: any;
- submitActionObservable: () => Observable<any>;
- submitAction: Function;
- deletionForm: CdFormGroup;
- itemDescription: 'entry';
- actionDescription = 'delete';
-
- constructor(public modalRef: BsModalRef) {}
-
- ngOnInit() {
- this.deletionForm = new CdFormGroup({
- confirmation: new FormControl(false, [Validators.requiredTrue])
- });
-
- if (!(this.submitAction || this.submitActionObservable)) {
- throw new Error('No submit action defined');
- }
- }
-
- callSubmitAction() {
- if (this.submitActionObservable) {
- this.submitActionObservable().subscribe(
- null,
- this.stopLoadingSpinner.bind(this),
- this.hideModal.bind(this)
- );
- } else {
- this.submitAction();
- }
- }
-
- hideModal() {
- this.modalRef.hide();
- }
-
- stopLoadingSpinner() {
- this.deletionForm.setErrors({ cdSubmitButton: true });
- }
-}