I read the Angular Testing and I'm not sure if there is any reference about testing elements inside a modal and how to check custom actions. My purpose is to write the necessary tests show I will be sure that my function and the modal works as expected.
As the modal is hidden, the tests to check if the elements of the modal appear, fail. So I suppose that there is something missing here.
This is my photos.components.ts
file:
import {Component, OnInit, ViewEncapsulation} from '@angular/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'app-photos',
templateUrl: './photos.component.html',
styleUrls: ['./photos.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class PhotosComponent implements OnInit {
constructor(private modalService: NgbModal) { }
openDarkModal(content) {
this.modalService.open(content, { windowClass: 'dark-modal', size: 'lg', centered: true });
}
ngOnInit() {
}
}
This is my photos.component.html
file:
<div>
<div class="col-lg-4 col-sm-6 mb-3">
<a><img (click)="openDarkModal(content)" id="photo-one" class="img-fluid z-depth-4 relative waves-light" src="#" alt="Image" data-toggle="content" data-target="#content"></a>
</div>
</div>
<!-- Dark Modal -->
<ng-template #content let-modal id="ng-modal">
<div class="modal-header dark-modal">
<img (click)="modal.dismiss('Cross click')" id="modal-image" class="embed-responsive-item img-fluid" src="#" alt="Image" allowfullscreen>
</div>
<div class="justify-content-center flex-column flex-md-row list-inline">
<ul class="list-inline flex-center text-align-center text-decoration-none" id="modal-buttons-list">
<li><a style="color: white;" href="#"><button mdbBtn type="button" size="sm" class="waves-light" color="indigo" mdbWavesEffect><i class="fab fa-facebook-f"></i></button></a></li>
<li><a style="color: white;" href="#"><button mdbBtn type="button" size="sm" class="waves-light" color="cyan" mdbWavesEffect><i class="fab fa-twitter"></i></button></a></li>
<li><a style="color: white;" href="#"><button mdbBtn type="button" size="sm" class="waves-light btn btn-blue-grey" mdbWavesEffect><i class="fas fa-envelope"></i></button></a></li>
</ul>
</div>
</ng-template>
and this is where I am with the photos.component.spec.ts
file:
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PhotosComponent } from './photos.component';
import { NO_ERRORS_SCHEMA } from '@angular/core';
describe('PhotosComponent', () => {
let component: PhotosComponent;
let fixture: ComponentFixture<PhotosComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PhotosComponent ],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PhotosComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should render the first photo', () => {
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('#photo-one')).toBeTruthy();
});
});
I need the test cases for the elements inside the Dark Modal and the test for the openDarkModal. Except for the code, a reference in Angular 7 testing for beginners would be appreciated.
For click event we can use triggerEventHandler method of Angular DebugElement class. We can also call native JavaScript click method of button. On click of button, we call a component method and it is possible that our component method has other dependencies to execute.
fixture. detectChanges() tells Angular to run change-detection. Finally! Every time it is called, it updates data bindings like ng-if, and re-renders the component based on the updated data. Calling this function will cause ngOnInit to run only the first time it is called.
TestBed is the primary api for writing unit tests for Angular applications and libraries.
Let me help you with this one. Lets say you have
app.component.html
<div id="title">
{{title}}
</div>
<ng-template #content
let-modal
id="ng-modal">
<div class="modal-header dark-modal">
Header
</div>
<div class="justify-content-center flex-column flex-md-row list-inline">
Body
</div>
</ng-template>
app.component.ts
import { Component, ViewChild, TemplateRef } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
title = 'AngularProj';
@ViewChild('content') modalRef: TemplateRef<any>;
}
You need to write spec
file with slightly different way:
app.component.spec.ts
import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { ViewChild, Component, OnInit, AfterContentInit, TemplateRef } from '@angular/core';
import { By } from '@angular/platform-browser';
@Component({
template: `
<ng-container *ngTemplateOutlet="modal"> </ng-container>
<app-root></app-root>
`,
})
class WrapperComponent implements AfterContentInit {
@ViewChild(AppComponent) appComponentRef: AppComponent;
modal: TemplateRef<any>;
ngAfterContentInit() {
this.modal = this.appComponentRef.modalRef;
}
}
describe('AppComponent', () => {
let app: AppComponent;
let fixture: ComponentFixture<WrapperComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [WrapperComponent, AppComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(WrapperComponent);
const wrapperComponent = fixture.debugElement.componentInstance;
app = wrapperComponent.appComponentRef;
fixture.detectChanges();
});
it('should create the app', async(() => {
expect(app).toBeDefined();
}));
it('should have title in HtmL ', () => {
const titleText = (fixture.debugElement.nativeElement.querySelector('#title').innerText);
expect(titleText).toBe('AngularProj');
});
it('should have Header in HtmL ', () => {
const headerText = (fixture.debugElement.queryAll(By.css('.modal-header.dark-modal'))[0].nativeElement.innerText);
expect(headerText).toBe('Header');
});
});
app-root
with a sample testing component (WrapperComponent
).app-root
has ng-template
, so it won't render on it's own. This creates a tricky situation as we need to render this part of the app.component
.ng-template
by creating @ViewChild('content') modalRef: TemplateRef<any>;
and then using it to render inside WrapperComponent
.I know it seems like a hack but over all the articles I went through, that's how we can achieve this.
For testing something like:
openDarkModal(content) {
this.modalService.open(content, { windowClass: 'dark-modal', size: 'lg', centered: true });
}
you can use spy
, but before that make modalService
public so that it can be spied upon:
constructor(public modalService: NgbModal) { }
You can also use jasmine.createSpyObj
and keep the service private
.
and then in spec
:
import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap';
TestBed.configureTestingModule({
imports: [NgbModalModule],
declarations: [PhotosComponent, /*WrapperComponent*/],
schemas: [NO_ERRORS_SCHEMA],
})
// and in it block
it('should call modal Service open function when clicked ', async(() => {
spyOn(component.modalService,'open').and.callThrough();
const openModalEle= fixture.debugElement.nativeElement.querySelector('#photo-one'));
openModalEle.click();
expect(component.modalService.open).toHaveBeenCalled();
}));
with Angular 11, there are few changes which you will require:
@Component({
template: `
<div>
<ng-container *ngTemplateOutlet="modal"> </ng-container>
</div>
<app-root> </app-root>
`,
})
class WrapperComponent implements AfterViewInit {
@ViewChild(AppComponent) appComponentRef: AppComponent;
modal: TemplateRef<any>;
constructor(private cdr: ChangeDetectorRef) {}
ngAfterViewInit() {
this.modal = this.appComponentRef.modalRef;
this.cdr.detectChanges();
}
}
describe('AppComponent', () => {
let fixture: ComponentFixture<WrapperComponent>;
let wrapperComponent: WrapperComponent;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [WrapperComponent, AppComponent],
imports: [ModalModule.forRoot()],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(WrapperComponent);
wrapperComponent = fixture.debugElement.componentInstance;
fixture.detectChanges();
});
it('should create the app', () => {
expect(wrapperComponent).toBeDefined();
expect(wrapperComponent.appComponentRef).toBeDefined();
});
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With