test(core): convert listener TemplateFixture tests to TestBed (#46509)

Converts listeners tests with hand-written generated code to
TestBed fixtures.

PR Close #46509
This commit is contained in:
Pawel Kozlowski
2022-06-27 14:15:30 +02:00
committed by Dylan Hunn
parent 779da7f9c4
commit e42db01dc0
2 changed files with 290 additions and 516 deletions
+290 -2
View File
@@ -16,7 +16,99 @@ function getNoOfNativeListeners(): number {
}
describe('event listeners', () => {
describe('even handler', () => {
describe('even handling statements', () => {
it('should call function on event emit', () => {
@Component({
template: `<button (click)="onClick()">Click me</button>`,
})
class MyComp {
counter = 0;
onClick() {
this.counter++;
}
}
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(fixture.componentInstance.counter).toEqual(0);
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(fixture.componentInstance.counter).toEqual(1);
});
it('should call function chain on event emit', () => {
@Component({
template: `<button (click)="onClick(); onClick2(); "> Click me </button>`,
})
class MyComp {
counter = 0;
counter2 = 0;
onClick() {
this.counter++;
}
onClick2() {
this.counter2++;
}
}
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(fixture.componentInstance.counter).toEqual(0);
expect(fixture.componentInstance.counter2).toEqual(0);
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(fixture.componentInstance.counter).toEqual(1);
expect(fixture.componentInstance.counter2).toEqual(1);
});
it('should evaluate expression on event emit', () => {
@Component({
template: `<button (click)="showing=!showing"> Click me </button>`,
})
class MyComp {
showing = false;
}
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(fixture.componentInstance.showing).toBeFalse();
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(fixture.componentInstance.showing).toBeTrue();
button.click();
expect(fixture.componentInstance.showing).toBeFalse();
});
it('should support listeners with specified set of args', () => {
@Component({
template: `<button (click)="onClick(data.a, data.b)"> Click me </button>`,
})
class MyComp {
counter = 0;
data = {a: 1, b: 2};
onClick(a: any, b: any) {
this.counter += a + b;
}
}
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(fixture.componentInstance.counter).toBe(0);
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(fixture.componentInstance.counter).toBe(3);
button.click();
expect(fixture.componentInstance.counter).toBe(6);
});
it('should be able to access a property called $event using `this`', () => {
let eventVariable: number|undefined;
let eventObject: MouseEvent|undefined;
@@ -110,6 +202,82 @@ describe('event listeners', () => {
expect(instance.one.value).toBe('one!');
expect(instance.two.value).toBe('two!');
});
it('should support local refs in listeners', () => {
@Component({
selector: 'my-comp',
standalone: true,
template: ``,
})
class MyComp {
}
@Component({
standalone: true,
imports: [MyComp],
template: `
<my-comp #comp></my-comp>
<button (click)="onClick(comp)"></button>
`,
})
class App {
comp: MyComp|null = null;
onClick(comp: MyComp) {
this.comp = comp;
}
}
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
expect(fixture.componentInstance.comp).toBeNull();
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(fixture.componentInstance.comp).toBeInstanceOf(MyComp);
});
});
describe('prevent default', () => {
it('should call prevent default when a handler returns false', () => {
@Component({
template: `<button (click)="onClick($event)">Click</button>`,
})
class MyComp {
handlerReturnValue: boolean|undefined;
event: Event|undefined;
onClick(e: any) {
this.event = e;
// stub preventDefault() to check whether it's called
Object.defineProperty(
this.event, 'preventDefault',
{value: jasmine.createSpy('preventDefault'), writable: true});
return this.handlerReturnValue;
}
}
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const myComp = fixture.componentInstance;
const button = fixture.nativeElement.querySelector('button');
myComp.handlerReturnValue = undefined;
button.click();
expect(myComp.event!.preventDefault).not.toHaveBeenCalled();
myComp.handlerReturnValue = true;
button.click();
expect(myComp.event!.preventDefault).not.toHaveBeenCalled();
// Returning `false` is what causes the renderer to call `event.preventDefault`.
myComp.handlerReturnValue = false;
button.click();
expect(myComp.event!.preventDefault).toHaveBeenCalled();
});
});
describe('coalescing', () => {
@@ -490,7 +658,127 @@ describe('event listeners', () => {
});
});
describe('global event listeners on special elements', () => {
describe('host listeners', () => {
it('should support host listeners on components', () => {
const events: string[] = [];
@Component({
template: ``,
})
class MyComp {
@HostListener('click')
onClick() {
events.push('click!');
}
}
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const host = fixture.nativeElement;
host.click();
expect(events).toEqual(['click!']);
host.click();
expect(events).toEqual(['click!', 'click!']);
});
it('should support global host listeners on components', () => {
const events: string[] = [];
@Component({
template: ``,
})
class MyComp {
@HostListener('document:click')
onClick() {
events.push('global click!');
}
}
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const host = fixture.nativeElement;
host.click();
expect(events).toEqual(['global click!']);
host.click();
expect(events).toEqual(['global click!', 'global click!']);
});
it('should support host listeners on directives', () => {
const events: string[] = [];
@Directive({
selector: '[hostListenerDir]',
standalone: true,
})
class HostListenerDir {
@HostListener('click')
onClick() {
events.push('click!');
}
}
@Component({
standalone: true,
imports: [HostListenerDir],
template: `<button hostListenerDir>Click</button>`,
})
class MyComp {
}
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(events).toEqual([]);
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(events).toEqual(['click!']);
button.click();
expect(events).toEqual(['click!', 'click!']);
});
it('should support global host listeners on directives', () => {
const events: string[] = [];
@Directive({
selector: '[hostListenerDir]',
standalone: true,
})
class HostListenerDir {
@HostListener('document:click')
onClick() {
events.push('click!');
}
}
@Component({
standalone: true,
imports: [HostListenerDir],
template: `<button hostListenerDir>Click</button>`,
})
class MyComp {
}
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(events).toEqual([]);
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(events).toEqual(['click!']);
button.click();
expect(events).toEqual(['click!', 'click!']);
});
});
describe('global host listeners on special element as directive hosts', () => {
it('should bind global event listeners on an ng-container directive host', () => {
let clicks = 0;
@@ -1,514 +0,0 @@
/**
* @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 {HEADER_OFFSET} from '@angular/core/src/render3/interfaces/view';
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
import {ɵɵdefineComponent, ɵɵdefineDirective, ɵɵreference, ɵɵresolveBody, ɵɵresolveDocument} from '../../src/render3/index';
import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵgetCurrentView, ɵɵlistener, ɵɵtext} from '../../src/render3/instructions/all';
import {RenderFlags} from '../../src/render3/interfaces/definition';
import {GlobalTargetResolver} from '../../src/render3/interfaces/renderer';
import {ɵɵresetView, ɵɵrestoreView} from '../../src/render3/state';
import {getRendererFactory2} from './imported_renderer2';
import {ComponentFixture, containerEl, createComponent, getDirectiveOnNode, renderToHtml, TemplateFixture} from './render_util';
describe('event listeners', () => {
let comps: any[] = [];
let events: any[] = [];
class MyComp {
showing = true;
counter = 0;
onClick() {
this.counter++;
}
static ɵfac =
() => {
let comp = new MyComp();
comps.push(comp);
return comp;
}
static ɵcmp = ɵɵdefineComponent({
type: MyComp,
selectors: [['comp']],
decls: 2,
vars: 0,
/** <button (click)="onClick()"> Click me </button> */
template:
function CompTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelementStart(0, 'button');
{
ɵɵlistener('click', function() {
return ctx.onClick();
});
ɵɵtext(1, 'Click me');
}
ɵɵelementEnd();
}
}
});
}
class MyCompWithGlobalListeners {
/* @HostListener('document:custom') */
onDocumentCustomEvent() {
events.push('component - document:custom');
}
/* @HostListener('body:click') */
onBodyClick() {
events.push('component - body:click');
}
static ɵfac =
() => {
let comp = new MyCompWithGlobalListeners();
comps.push(comp);
return comp;
}
static ɵcmp = ɵɵdefineComponent({
type: MyCompWithGlobalListeners,
selectors: [['comp']],
decls: 1,
vars: 0,
template:
function CompTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵtext(0, 'Some text');
}
},
hostBindings:
function HostListenerDir_HostBindings(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵlistener('custom', function() {
return ctx.onDocumentCustomEvent();
}, false, ɵɵresolveDocument as GlobalTargetResolver);
ɵɵlistener('click', function() {
return ctx.onBodyClick();
}, false, ɵɵresolveBody as GlobalTargetResolver);
}
}
});
}
class GlobalHostListenerDir {
/* @HostListener('document:custom') */
onDocumentCustomEvent() {
events.push('directive - document:custom');
}
/* @HostListener('body:click') */
onBodyClick() {
events.push('directive - body:click');
}
static ɵfac = function HostListenerDir_Factory() {
return new GlobalHostListenerDir();
};
static ɵdir = ɵɵdefineDirective({
type: GlobalHostListenerDir,
selectors: [['', 'hostListenerDir', '']],
hostBindings:
function HostListenerDir_HostBindings(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵlistener('custom', function() {
return ctx.onDocumentCustomEvent();
}, false, ɵɵresolveDocument as GlobalTargetResolver)('click', function() {
return ctx.onBodyClick();
}, false, ɵɵresolveBody as GlobalTargetResolver);
}
}
});
}
class PreventDefaultComp {
handlerReturnValue: any = true;
// TODO(issue/24571): remove '!'.
event!: Event;
onClick(e: any) {
this.event = e;
// stub preventDefault() to check whether it's called
Object.defineProperty(
this.event, 'preventDefault',
{value: jasmine.createSpy('preventDefault'), writable: true});
return this.handlerReturnValue;
}
static ɵfac = () => new PreventDefaultComp();
static ɵcmp = ɵɵdefineComponent({
type: PreventDefaultComp,
selectors: [['prevent-default-comp']],
decls: 2,
vars: 0,
/** <button (click)="onClick($event)">Click</button> */
template:
(rf: RenderFlags, ctx: PreventDefaultComp) => {
if (rf & RenderFlags.Create) {
ɵɵelementStart(0, 'button');
{
ɵɵlistener('click', function($event: any) {
return ctx.onClick($event);
});
ɵɵtext(1, 'Click');
}
ɵɵelementEnd();
}
}
});
}
beforeEach(() => {
comps = [];
events = [];
});
it('should call function on event emit', () => {
const fixture = new ComponentFixture(MyComp);
const comp = fixture.component;
const button = fixture.hostElement.querySelector('button')!;
button.click();
expect(comp.counter).toEqual(1);
button.click();
expect(comp.counter).toEqual(2);
});
it('should retain event handler return values using document', () => {
const fixture = new ComponentFixture(PreventDefaultComp);
const preventDefaultComp = fixture.component;
const button = fixture.hostElement.querySelector('button')!;
button.click();
expect(preventDefaultComp.event!.preventDefault).not.toHaveBeenCalled();
preventDefaultComp.handlerReturnValue = undefined;
button.click();
expect(preventDefaultComp.event!.preventDefault).not.toHaveBeenCalled();
preventDefaultComp.handlerReturnValue = false;
button.click();
expect(preventDefaultComp.event!.preventDefault).toHaveBeenCalled();
});
it('should retain event handler return values with renderer2', () => {
const fixture =
new ComponentFixture(PreventDefaultComp, {rendererFactory: getRendererFactory2(document)});
const preventDefaultComp = fixture.component;
const button = fixture.hostElement.querySelector('button')!;
button.click();
expect(preventDefaultComp.event!.preventDefault).not.toHaveBeenCalled();
preventDefaultComp.handlerReturnValue = undefined;
button.click();
expect(preventDefaultComp.event!.preventDefault).not.toHaveBeenCalled();
preventDefaultComp.handlerReturnValue = false;
button.click();
expect(preventDefaultComp.event!.preventDefault).toHaveBeenCalled();
});
it('should call function chain on event emit', () => {
/** <button (click)="onClick(); onClick2(); "> Click me </button> */
function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelementStart(0, 'button');
{
ɵɵlistener('click', function() {
ctx.onClick();
return ctx.onClick2();
});
ɵɵtext(1, 'Click me');
}
ɵɵelementEnd();
}
}
const ctx = {
counter: 0,
counter2: 0,
onClick: function() {
this.counter++;
},
onClick2: function() {
this.counter2++;
}
};
renderToHtml(Template, ctx, 2);
const button = containerEl.querySelector('button')!;
button.click();
expect(ctx.counter).toBe(1);
expect(ctx.counter2).toBe(1);
button.click();
expect(ctx.counter).toBe(2);
expect(ctx.counter2).toBe(2);
});
it('should evaluate expression on event emit', () => {
/** <button (click)="showing=!showing"> Click me </button> */
function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelementStart(0, 'button');
{
ɵɵlistener('click', function() {
return ctx.showing = !ctx.showing;
});
ɵɵtext(1, 'Click me');
}
ɵɵelementEnd();
}
}
const ctx = {showing: false};
renderToHtml(Template, ctx, 2);
const button = containerEl.querySelector('button')!;
button.click();
expect(ctx.showing).toBe(true);
button.click();
expect(ctx.showing).toBe(false);
});
it('should support host listeners on components', () => {
let events: string[] = [];
class MyComp {
/* @HostListener('click') */
onClick() {
events.push('click!');
}
static ɵfac =
() => {
return new MyComp();
}
static ɵcmp = ɵɵdefineComponent({
type: MyComp,
selectors: [['comp']],
decls: 1,
vars: 0,
template:
function CompTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵtext(0, 'Some text');
}
},
hostBindings:
function HostListenerDir_HostBindings(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵlistener('click', function() {
return ctx.onClick();
});
}
}
});
}
const fixture = new ComponentFixture(MyComp);
const host = fixture.hostElement;
host.click();
expect(events).toEqual(['click!']);
host.click();
expect(events).toEqual(['click!', 'click!']);
});
it('should support global host listeners on components', () => {
const fixture = new ComponentFixture(MyCompWithGlobalListeners);
const doc = fixture.hostElement.ownerDocument!;
dispatchEvent(doc, 'custom');
expect(events).toEqual(['component - document:custom']);
dispatchEvent(doc.body, 'click');
expect(events).toEqual(['component - document:custom', 'component - body:click']);
// invoke destroy for this fixture to cleanup all listeners setup for global objects
fixture.destroy();
});
it('should support host listeners on directives', () => {
let events: string[] = [];
class HostListenerDir {
/* @HostListener('click') */
onClick() {
events.push('click!');
}
static ɵfac = function HostListenerDir_Factory() {
return new HostListenerDir();
};
static ɵdir = ɵɵdefineDirective({
type: HostListenerDir,
selectors: [['', 'hostListenerDir', '']],
hostBindings:
function HostListenerDir_HostBindings(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵlistener('click', function() {
return ctx.onClick();
});
}
}
});
}
const fixture = new TemplateFixture({
create: () => {
ɵɵelementStart(0, 'button', 0);
ɵɵtext(1, 'Click');
ɵɵelementEnd();
},
decls: 2,
directives: [HostListenerDir],
consts: [['hostListenerDir', '']]
});
const button = fixture.hostElement.querySelector('button')!;
button.click();
expect(events).toEqual(['click!']);
button.click();
expect(events).toEqual(['click!', 'click!']);
});
it('should support global host listeners on directives', () => {
const fixture = new TemplateFixture({
create: () => {
ɵɵelement(0, 'div', 0);
},
decls: 1,
directives: [GlobalHostListenerDir],
consts: [['hostListenerDir', '']]
});
const doc = fixture.hostElement.ownerDocument!;
dispatchEvent(doc, 'custom');
expect(events).toEqual(['directive - document:custom']);
dispatchEvent(doc.body, 'click');
expect(events).toEqual(['directive - document:custom', 'directive - body:click']);
// invoke destroy for this fixture to cleanup all listeners setup for global objects
fixture.destroy();
});
it('should support listeners with specified set of args', () => {
class MyComp {
counter = 0;
data = {a: 1, b: 2};
onClick(a: any, b: any) {
this.counter += a + b;
}
static ɵfac = () => new MyComp();
static ɵcmp = ɵɵdefineComponent({
type: MyComp,
selectors: [['comp']],
decls: 2,
vars: 0,
/** <button (click)="onClick(data.a, data.b)"> Click me </button> */
template:
function CompTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelementStart(0, 'button');
{
ɵɵlistener('click', function() {
return ctx.onClick(ctx.data.a, ctx.data.b);
});
ɵɵtext(1, 'Click me');
}
ɵɵelementEnd();
}
}
});
}
const fixture = new ComponentFixture(MyComp);
const comp = fixture.component;
const button = fixture.hostElement.querySelector('button')!;
button.click();
expect(comp.counter).toEqual(3);
button.click();
expect(comp.counter).toEqual(6);
});
it('should support local refs in listeners', () => {
let compInstance: any;
const Comp = createComponent('comp', (rf: RenderFlags, ctx: any) => {});
/**
* <comp #comp></comp>
* <button (click)="onClick(comp)"></button>
*/
class App {
comp: any = null;
onClick(comp: any) {
this.comp = comp;
}
static ɵfac = () => new App();
static ɵcmp = ɵɵdefineComponent({
type: App,
selectors: [['app']],
decls: 3,
vars: 0,
consts: [['comp', '']],
template:
(rf: RenderFlags, ctx: App) => {
if (rf & RenderFlags.Create) {
const state = ɵɵgetCurrentView();
ɵɵelement(0, 'comp', null, 0);
ɵɵelementStart(2, 'button');
{
ɵɵlistener('click', function() {
ɵɵrestoreView(state);
const comp = ɵɵreference(1);
return ɵɵresetView(ctx.onClick(comp));
});
}
ɵɵelementEnd();
}
// testing only
compInstance = getDirectiveOnNode(HEADER_OFFSET);
},
dependencies: [Comp]
});
}
const fixture = new ComponentFixture(App);
expect(fixture.component.comp).toEqual(null);
const button = fixture.hostElement.querySelector('button') as HTMLButtonElement;
button.click();
expect(fixture.component.comp).toEqual(compInstance);
});
});