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