Kristiyan Kostadinov a787f78074 test: clean up internal testing utilities (#42177)
We have some internal proxies for all of the Jasmine functions, as well as some other helpers. This code hasn't been touched in more than 5 years, it can lead to confusion and it isn't really necessary since the same can be achieved using Jasmine.

These changes remove most of the code and clean up our existing unit tests.

PR Close #42177
2021-05-26 20:07:25 +00:00

347 lines
12 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ProfilerEvent, setProfiler} from '@angular/core/src/render3/profiler';
import {TestBed} from '@angular/core/testing';
import {onlyInIvy} from '@angular/private/testing';
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, Component, DoCheck, ErrorHandler, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, ViewChild} from '../../src/core';
onlyInIvy('Ivy-specific functionality').describe('profiler', () => {
class Profiler {
profile() {}
}
let profilerSpy: jasmine.Spy;
beforeEach(() => {
const profiler = new Profiler();
profilerSpy = spyOn(profiler, 'profile').and.callThrough();
setProfiler(profiler.profile);
});
afterAll(() => setProfiler(null));
function findProfilerCall(condition: ProfilerEvent|((args: any[]) => boolean)) {
let predicate: (args: any[]) => boolean = _ => true;
if (typeof condition !== 'function') {
predicate = (args: any[]) => args[0] === condition;
} else {
predicate = condition;
}
return profilerSpy.calls.all().map((call: any) => call.args).find(predicate);
}
describe('change detection hooks', () => {
it('should call the profiler for creation and change detection', () => {
@Component({selector: 'my-comp', template: '<button (click)="onClick()"></button>'})
class MyComponent {
onClick() {}
}
TestBed.configureTestingModule({declarations: [MyComponent]});
const fixture = TestBed.createComponent(MyComponent);
expect(profilerSpy).toHaveBeenCalled();
const templateCreateStart = findProfilerCall(
(args: any[]) => args[0] === ProfilerEvent.TemplateCreateStart &&
args[1] === fixture.componentInstance);
const templateCreateEnd = findProfilerCall(
(args: any[]) =>
args[0] === ProfilerEvent.TemplateCreateEnd && args[1] === fixture.componentInstance);
expect(templateCreateStart).toBeTruthy();
expect(templateCreateEnd).toBeTruthy();
fixture.detectChanges();
const templateUpdateStart = findProfilerCall(
(args: any[]) => args[0] === ProfilerEvent.TemplateUpdateStart &&
args[1] === fixture.componentInstance);
const templateUpdateEnd = findProfilerCall(
(args: any[]) =>
args[0] === ProfilerEvent.TemplateUpdateEnd && args[1] === fixture.componentInstance);
expect(templateUpdateStart).toBeTruthy();
expect(templateUpdateEnd).toBeTruthy();
});
it('should invoke the profiler when the template throws', () => {
@Component({selector: 'my-comp', template: '{{ throw() }}'})
class MyComponent {
throw() {
throw new Error();
}
}
TestBed.configureTestingModule({declarations: [MyComponent]});
let myComp: MyComponent;
expect(() => {
const fixture = TestBed.createComponent(MyComponent);
myComp = fixture.componentInstance;
fixture.detectChanges();
}).toThrow();
expect(profilerSpy).toHaveBeenCalled();
const templateCreateStart = findProfilerCall(
(args: any[]) => args[0] === ProfilerEvent.TemplateCreateStart && args[1] === myComp);
const templateCreateEnd = findProfilerCall(
(args: any[]) => args[0] === ProfilerEvent.TemplateCreateEnd && args[1] === myComp);
expect(templateCreateStart).toBeTruthy();
expect(templateCreateEnd).toBeTruthy();
});
});
describe('outputs and events', () => {
it('should invoke the profiler on event handler', () => {
@Component({selector: 'my-comp', template: '<button (click)="onClick()"></button>'})
class MyComponent {
onClick() {}
}
TestBed.configureTestingModule({declarations: [MyComponent]});
const fixture = TestBed.createComponent(MyComponent);
const myComp = fixture.componentInstance;
const clickSpy = spyOn(myComp, 'onClick');
const button = fixture.nativeElement.querySelector('button')!;
button.click();
expect(clickSpy).toHaveBeenCalled();
const outputStart = findProfilerCall(ProfilerEvent.OutputStart);
const outputEnd = findProfilerCall(ProfilerEvent.OutputEnd);
expect(outputStart[1]).toEqual(myComp!);
expect(outputEnd[1]).toEqual(myComp!);
});
it('should invoke the profiler on event handler even when it throws', () => {
@Component({selector: 'my-comp', template: '<button (click)="onClick()"></button>'})
class MyComponent {
onClick() {
throw new Error();
}
}
const handler = new ErrorHandler();
const errorSpy = spyOn(handler, 'handleError');
TestBed.configureTestingModule(
{declarations: [MyComponent], providers: [{provide: ErrorHandler, useValue: handler}]});
const fixture = TestBed.createComponent(MyComponent);
const myComp = fixture.componentInstance;
const button = fixture.nativeElement.querySelector('button')!;
button.click();
expect(errorSpy).toHaveBeenCalled();
const outputStart = findProfilerCall(ProfilerEvent.OutputStart);
const outputEnd = findProfilerCall(ProfilerEvent.OutputEnd);
expect(outputStart[1]).toEqual(myComp!);
expect(outputEnd[1]).toEqual(myComp!);
});
it('should invoke the profiler on output handler execution', async () => {
@Component({selector: 'child', template: ''})
class Child {
@Output() childEvent = new EventEmitter();
}
@Component({selector: 'my-comp', template: '<child (childEvent)="onEvent()"></child>'})
class MyComponent {
@ViewChild(Child) child!: Child;
onEvent() {}
}
TestBed.configureTestingModule({declarations: [MyComponent, Child]});
const fixture = TestBed.createComponent(MyComponent);
const myComp = fixture.componentInstance;
fixture.detectChanges();
myComp.child!.childEvent.emit();
const outputStart = findProfilerCall(ProfilerEvent.OutputStart);
const outputEnd = findProfilerCall(ProfilerEvent.OutputEnd);
expect(outputStart[1]).toEqual(myComp!);
expect(outputEnd[1]).toEqual(myComp!);
});
});
describe('lifecycle hooks', () => {
it('should call the profiler on lifecycle execution', () => {
class Service implements OnDestroy {
ngOnDestroy() {}
}
@Component({selector: 'my-comp', template: '{{prop}}', providers: [Service]})
class MyComponent implements OnInit, AfterViewInit, AfterViewChecked, AfterContentInit,
AfterContentChecked, OnChanges, DoCheck, OnDestroy {
@Input() prop = 1;
constructor(private service: Service) {}
ngOnInit() {}
ngDoCheck() {}
ngOnDestroy() {}
ngOnChanges() {}
ngAfterViewInit() {}
ngAfterViewChecked() {}
ngAfterContentInit() {}
ngAfterContentChecked() {}
}
@Component({selector: 'my-parent', template: '<my-comp [prop]="prop"></my-comp>'})
class MyParent {
prop = 1;
@ViewChild(MyComponent) child!: MyComponent;
}
TestBed.configureTestingModule({declarations: [MyParent, MyComponent]});
const fixture = TestBed.createComponent(MyParent);
fixture.detectChanges();
const myParent = fixture.componentInstance;
const myComp = fixture.componentInstance.child;
const ngOnInitStart = findProfilerCall(
(args: any[]) =>
args[0] === ProfilerEvent.LifecycleHookStart && args[2] === myComp.ngOnInit);
const ngOnInitEnd = findProfilerCall(
(args: any[]) =>
args[0] === ProfilerEvent.LifecycleHookEnd && args[2] === myComp.ngOnInit);
expect(ngOnInitStart).toBeTruthy();
expect(ngOnInitEnd).toBeTruthy();
const ngOnDoCheckStart = findProfilerCall(
(args: any[]) =>
args[0] === ProfilerEvent.LifecycleHookStart && args[2] === myComp.ngDoCheck);
const ngOnDoCheckEnd = findProfilerCall(
(args: any[]) =>
args[0] === ProfilerEvent.LifecycleHookEnd && args[2] === myComp.ngDoCheck);
expect(ngOnDoCheckStart).toBeTruthy();
expect(ngOnDoCheckEnd).toBeTruthy();
const ngAfterViewInitStart = findProfilerCall(
(args: any[]) =>
args[0] === ProfilerEvent.LifecycleHookStart && args[2] === myComp.ngAfterViewInit);
const ngAfterViewInitEnd = findProfilerCall(
(args: any[]) =>
args[0] === ProfilerEvent.LifecycleHookEnd && args[2] === myComp.ngAfterViewInit);
expect(ngAfterViewInitStart).toBeTruthy();
expect(ngAfterViewInitEnd).toBeTruthy();
const ngAfterViewCheckedStart = findProfilerCall(
(args: any[]) => args[0] === ProfilerEvent.LifecycleHookStart &&
args[2] === myComp.ngAfterViewChecked);
const ngAfterViewCheckedEnd = findProfilerCall(
(args: any[]) =>
args[0] === ProfilerEvent.LifecycleHookEnd && args[2] === myComp.ngAfterViewChecked);
expect(ngAfterViewCheckedStart).toBeTruthy();
expect(ngAfterViewCheckedEnd).toBeTruthy();
const ngAfterContentInitStart = findProfilerCall(
(args: any[]) => args[0] === ProfilerEvent.LifecycleHookStart &&
args[2] === myComp.ngAfterContentInit);
const ngAfterContentInitEnd = findProfilerCall(
(args: any[]) =>
args[0] === ProfilerEvent.LifecycleHookEnd && args[2] === myComp.ngAfterContentInit);
expect(ngAfterContentInitStart).toBeTruthy();
expect(ngAfterContentInitEnd).toBeTruthy();
const ngAfterContentCheckedStart = findProfilerCall(
(args: any[]) => args[0] === ProfilerEvent.LifecycleHookStart &&
args[2] === myComp.ngAfterContentChecked);
const ngAfterContentChecked = findProfilerCall(
(args: any[]) => args[0] === ProfilerEvent.LifecycleHookEnd &&
args[2] === myComp.ngAfterContentChecked);
expect(ngAfterContentCheckedStart).toBeTruthy();
expect(ngAfterContentChecked).toBeTruthy();
// Verify we call `ngOnChanges` and the corresponding profiler hooks
const onChangesSpy = spyOn(myComp, 'ngOnChanges');
profilerSpy.calls.reset();
myParent.prop = 2;
fixture.detectChanges();
const ngOnChangesStart = findProfilerCall(
(args: any[]) => args[0] === ProfilerEvent.LifecycleHookStart && args[2] &&
args[2].name && args[2].name.indexOf('OnChangesHook') >= 0);
const ngOnChangesEnd = findProfilerCall(
(args: any[]) => args[0] === ProfilerEvent.LifecycleHookEnd && args[2] && args[2].name &&
args[2].name.indexOf('OnChangesHook') >= 0);
expect(onChangesSpy).toHaveBeenCalled();
expect(ngOnChangesStart).toBeTruthy();
expect(ngOnChangesEnd).toBeTruthy();
fixture.destroy();
const ngOnDestroyStart = findProfilerCall(
(args: any[]) =>
args[0] === ProfilerEvent.LifecycleHookStart && args[2] === myComp.ngOnDestroy);
const ngOnDestroyEnd = findProfilerCall(
(args: any[]) =>
args[0] === ProfilerEvent.LifecycleHookEnd && args[2] === myComp.ngOnDestroy);
expect(ngOnDestroyStart).toBeTruthy();
expect(ngOnDestroyEnd).toBeTruthy();
const serviceNgOnDestroyStart = findProfilerCall(
(args: any[]) => args[0] === ProfilerEvent.LifecycleHookStart &&
args[2] === Service.prototype.ngOnDestroy);
const serviceNgOnDestroyEnd = findProfilerCall(
(args: any[]) => args[0] === ProfilerEvent.LifecycleHookEnd &&
args[2] === Service.prototype.ngOnDestroy);
expect(serviceNgOnDestroyStart).toBeTruthy();
expect(serviceNgOnDestroyEnd).toBeTruthy();
});
});
it('should call the profiler on lifecycle execution even after error', () => {
@Component({selector: 'my-comp', template: ''})
class MyComponent implements OnInit {
ngOnInit() {
throw new Error();
}
}
TestBed.configureTestingModule({declarations: [MyComponent]});
const fixture = TestBed.createComponent(MyComponent);
expect(() => {
fixture.detectChanges();
}).toThrow();
const lifecycleStart = findProfilerCall(ProfilerEvent.LifecycleHookStart);
const lifecycleEnd = findProfilerCall(ProfilerEvent.LifecycleHookEnd);
expect(lifecycleStart).toBeTruthy();
expect(lifecycleEnd).toBeTruthy();
});
});