From 6b627f67db7ce63bc57a55ce09e0aa9971174b7e Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Fri, 16 Feb 2018 11:36:50 -0800 Subject: [PATCH] test(ivy): add missing host listener and host attribute binding tests (#22213) PR Close #22213 --- .../core/src/core_render3_private_export.ts | 1 + .../compiler_canonical_spec.ts | 97 ++++++++++++++++++- .../core/test/render3/integration_spec.ts | 37 ++++++- packages/core/test/render3/listeners_spec.ts | 84 ++++++++++++++-- packages/core/test/render3/outputs_spec.ts | 48 ++++++--- 5 files changed, 244 insertions(+), 23 deletions(-) diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 45992b9c5c..7b63931092 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -60,6 +60,7 @@ export { e as ɵe, p as ɵp, pD as ɵpD, + a as ɵa, s as ɵs, t as ɵt, v as ɵv, diff --git a/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts b/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts index de141cd4ac..accd1e85f9 100644 --- a/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts +++ b/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, ContentChild, ContentChildren, Directive, HostBinding, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../src/core'; +import {Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../src/core'; import * as $r3$ from '../../src/core_render3_private_export'; import {renderComponent, toHtml} from '../render_util'; @@ -174,7 +174,102 @@ describe('compiler specification', () => { } expect(renderComp(MyApp)).toEqual(`
`); + }); + it('should support host listeners', () => { + type $MyApp$ = MyApp; + + @Directive({selector: '[hostlistenerDir]'}) + class HostListenerDir { + @HostListener('click') + onClick() {} + + // NORMATIVE + static ngDirectiveDef = $r3$.ɵdefineDirective({ + type: HostListenerDir, + factory: function HostListenerDir_Factory() { + const $dir$ = new HostListenerDir(); + $r3$.ɵL('click', function HostListenerDir_click_Handler(event) { $dir$.onClick(); }); + return $dir$; + }, + }); + // /NORMATIVE + } + + const $e0_attrs$ = ['hostListenerDir', '']; + const $e0_dirs$ = [HostListenerDir]; + + @Component({ + selector: 'my-app', + template: ` + + ` + }) + class MyApp { + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'button', $e0_attrs$, $e0_dirs$); + $r3$.ɵT(2, 'Click'); + $r3$.ɵe(); + } + HostListenerDir.ngDirectiveDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + } + + expect(renderComp(MyApp)).toEqual(``); + }); + + it('should support bindings of host attributes', () => { + type $MyApp$ = MyApp; + + @Directive({selector: '[hostBindingDir]'}) + class HostBindingDir { + @HostBinding('attr.aria-label') label = 'some label'; + + // NORMATIVE + static ngDirectiveDef = $r3$.ɵdefineDirective({ + type: HostBindingDir, + factory: function HostBindingDir_Factory() { return new HostBindingDir(); }, + hostBindings: function HostBindingDir_HostBindings( + dirIndex: $number$, elIndex: $number$) { + $r3$.ɵa(elIndex, 'aria-label', $r3$.ɵb($r3$.ɵm(dirIndex).label)); + } + }); + // /NORMATIVE + } + + const $e0_attrs$ = ['hostBindingDir', '']; + const $e0_dirs$ = [HostBindingDir]; + + @Component({ + selector: 'my-app', + template: ` +
+ ` + }) + class MyApp { + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'div', $e0_attrs$, $e0_dirs$); + $r3$.ɵe(); + } + HostBindingDir.ngDirectiveDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + } + + expect(renderComp(MyApp)).toEqual(`
`); }); xit('should support structural directives', () => { diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 449c55f9a1..c7ba705a0b 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {defineComponent} from '../../src/render3/index'; +import {defineComponent, defineDirective} from '../../src/render3/index'; import {NO_CHANGE, bind, componentRefresh, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClass, elementEnd, elementProperty, elementStart, elementStyle, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, memory, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {containerEl, renderToHtml} from './render_util'; @@ -664,6 +664,41 @@ describe('render3 integration test', () => { expect(renderToHtml(Template, ctx)) .toEqual(''); }); + + it('should support host attribute bindings', () => { + let hostBindingDir: HostBindingDir; + + class HostBindingDir { + /* @HostBinding('attr.aria-label') */ + label = 'some label'; + + static ngDirectiveDef = defineDirective({ + type: HostBindingDir, + factory: function HostBindingDir_Factory() { + return hostBindingDir = new HostBindingDir(); + }, + hostBindings: function HostBindingDir_HostBindings(dirIndex: number, elIndex: number) { + elementAttribute(elIndex, 'aria-label', bind(memory(dirIndex).label)); + } + }); + } + + function Template(ctx: any, cm: boolean) { + if (cm) { + elementStart(0, 'div', ['hostBindingDir', ''], [HostBindingDir]); + elementEnd(); + } + HostBindingDir.ngDirectiveDef.h(1, 0); + componentRefresh(1, 0); + } + + expect(renderToHtml(Template, {})) + .toEqual(`
`); + + hostBindingDir !.label = 'other label'; + expect(renderToHtml(Template, {})) + .toEqual(`
`); + }); }); describe('elementStyle', () => { diff --git a/packages/core/test/render3/listeners_spec.ts b/packages/core/test/render3/listeners_spec.ts index ea58a8eae6..613b2c1cc4 100644 --- a/packages/core/test/render3/listeners_spec.ts +++ b/packages/core/test/render3/listeners_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {defineComponent} from '../../src/render3/index'; +import {defineComponent, defineDirective} from '../../src/render3/index'; import {componentRefresh, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, listener, text} from '../../src/render3/instructions'; import {containerEl, renderComponent, renderToHtml} from './render_util'; @@ -29,7 +29,7 @@ describe('event listeners', () => { if (cm) { elementStart(0, 'button'); { - listener('click', ctx.onClick.bind(ctx)); + listener('click', function() { ctx.onClick(); }); text(1, 'Click me'); } elementEnd(); @@ -55,6 +55,40 @@ describe('event listeners', () => { expect(comp.counter).toEqual(2); }); + it('should call function chain on event emit', () => { + /** */ + function Template(ctx: any, cm: boolean) { + if (cm) { + elementStart(0, 'button'); + { + listener('click', function() { + ctx.onClick(); + ctx.onClick2(); + }); + text(1, 'Click me'); + } + elementEnd(); + } + } + + const ctx = { + counter: 0, + counter2: 0, + onClick: function() { this.counter++; }, + onClick2: function() { this.counter2++; } + }; + renderToHtml(Template, ctx); + 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', () => { /** */ @@ -62,7 +96,7 @@ describe('event listeners', () => { if (cm) { elementStart(0, 'button'); { - listener('click', () => ctx.showing = !ctx.showing); + listener('click', function() { ctx.showing = !ctx.showing; }); text(1, 'Click me'); } elementEnd(); @@ -97,7 +131,7 @@ describe('event listeners', () => { if (embeddedViewStart(1)) { elementStart(0, 'button'); { - listener('click', ctx.onClick.bind(ctx)); + listener('click', function() { ctx.onClick(); }); text(1, 'Click me'); } elementEnd(); @@ -125,6 +159,42 @@ describe('event listeners', () => { expect(comp.counter).toEqual(2); }); + it('should support host listeners', () => { + let events: string[] = []; + + class HostListenerDir { + /* @HostListener('click') */ + onClick() { events.push('click!'); } + + static ngDirectiveDef = defineDirective({ + type: HostListenerDir, + factory: function HostListenerDir_Factory() { + const $dir$ = new HostListenerDir(); + listener('click', function() { $dir$.onClick(); }); + return $dir$; + }, + }); + } + + function Template(ctx: any, cm: boolean) { + if (cm) { + elementStart(0, 'button', ['hostListenerDir', ''], [HostListenerDir]); + text(2, 'Click'); + elementEnd(); + } + HostListenerDir.ngDirectiveDef.h(1, 0); + componentRefresh(1, 0); + } + + renderToHtml(Template, {}); + const button = containerEl.querySelector('button') !; + button.click(); + expect(events).toEqual(['click!']); + + button.click(); + expect(events).toEqual(['click!', 'click!']); + }); + it('should destroy listeners in nested views', () => { /** @@ -152,7 +222,7 @@ describe('event listeners', () => { if (embeddedViewStart(0)) { elementStart(0, 'button'); { - listener('click', ctx.onClick.bind(ctx)); + listener('click', function() { ctx.onClick(); }); text(1, 'Click'); } elementEnd(); @@ -267,7 +337,7 @@ describe('event listeners', () => { if (embeddedViewStart(0)) { elementStart(0, 'button'); { - listener('click', () => ctx.counter1++); + listener('click', function() { ctx.counter1++; }); text(1, 'Click'); } elementEnd(); @@ -282,7 +352,7 @@ describe('event listeners', () => { if (embeddedViewStart(0)) { elementStart(0, 'button'); { - listener('click', () => ctx.counter2++); + listener('click', function() { ctx.counter2++; }); text(1, 'Click'); } elementEnd(); diff --git a/packages/core/test/render3/outputs_spec.ts b/packages/core/test/render3/outputs_spec.ts index f1032ddb1c..12fffd7168 100644 --- a/packages/core/test/render3/outputs_spec.ts +++ b/packages/core/test/render3/outputs_spec.ts @@ -46,7 +46,9 @@ describe('outputs', () => { function Template(ctx: any, cm: boolean) { if (cm) { elementStart(0, ButtonToggle); - { listener('change', ctx.onChange.bind(ctx)); } + { + listener('change', function() { ctx.onChange(); }); + } elementEnd(); } ButtonToggle.ngComponentDef.h(1, 0); @@ -70,8 +72,8 @@ describe('outputs', () => { if (cm) { elementStart(0, ButtonToggle); { - listener('change', ctx.onChange.bind(ctx)); - listener('reset', ctx.onReset.bind(ctx)); + listener('change', function() { ctx.onChange(); }); + listener('reset', function() { ctx.onReset(); }); } elementEnd(); } @@ -96,7 +98,9 @@ describe('outputs', () => { function Template(ctx: any, cm: boolean) { if (cm) { elementStart(0, ButtonToggle); - { listener('change', () => ctx.counter++); } + { + listener('change', function() { ctx.counter++; }); + } elementEnd(); } ButtonToggle.ngComponentDef.h(1, 0); @@ -130,7 +134,9 @@ describe('outputs', () => { if (ctx.condition) { if (embeddedViewStart(0)) { elementStart(0, ButtonToggle); - { listener('change', ctx.onChange.bind(ctx)); } + { + listener('change', function() { ctx.onChange(); }); + } elementEnd(); } ButtonToggle.ngComponentDef.h(1, 0); @@ -180,7 +186,9 @@ describe('outputs', () => { if (ctx.condition2) { if (embeddedViewStart(0)) { elementStart(0, ButtonToggle); - { listener('change', ctx.onChange.bind(ctx)); } + { + listener('change', function() { ctx.onChange(); }); + } elementEnd(); } ButtonToggle.ngComponentDef.h(1, 0); @@ -241,12 +249,14 @@ describe('outputs', () => { if (embeddedViewStart(0)) { elementStart(0, 'button'); { - listener('click', ctx.onClick.bind(ctx)); + listener('click', function() { ctx.onClick(); }); text(1, 'Click me'); } elementEnd(); elementStart(2, ButtonToggle); - { listener('change', ctx.onChange.bind(ctx)); } + { + listener('change', function() { ctx.onChange(); }); + } elementEnd(); elementStart(4, DestroyComp); elementEnd(); @@ -300,7 +310,9 @@ describe('outputs', () => { function Template(ctx: any, cm: boolean) { if (cm) { elementStart(0, 'button', null, [MyButton]); - { listener('click', ctx.onClick.bind(ctx)); } + { + listener('click', function() { ctx.onClick(); }); + } elementEnd(); } } @@ -323,7 +335,9 @@ describe('outputs', () => { function Template(ctx: any, cm: boolean) { if (cm) { elementStart(0, ButtonToggle, null, [OtherDir]); - { listener('change', ctx.onChange.bind(ctx)); } + { + listener('change', function() { ctx.onChange(); }); + } elementEnd(); } ButtonToggle.ngComponentDef.h(1, 0); @@ -354,7 +368,9 @@ describe('outputs', () => { function Template(ctx: any, cm: boolean) { if (cm) { elementStart(0, ButtonToggle, null, [OtherDir]); - { listener('change', ctx.onChange.bind(ctx)); } + { + listener('change', function() { ctx.onChange(); }); + } elementEnd(); } elementProperty(0, 'change', bind(ctx.change)); @@ -387,7 +403,7 @@ describe('outputs', () => { if (cm) { elementStart(0, 'button'); { - listener('click', ctx.onClick.bind(ctx)); + listener('click', function() { ctx.onClick(); }); text(1, 'Click me'); } elementEnd(); @@ -398,7 +414,9 @@ describe('outputs', () => { if (ctx.condition) { if (embeddedViewStart(0)) { elementStart(0, ButtonToggle); - { listener('change', ctx.onChange.bind(ctx)); } + { + listener('change', function() { ctx.onChange(); }); + } elementEnd(); } ButtonToggle.ngComponentDef.h(1, 0); @@ -407,7 +425,9 @@ describe('outputs', () => { } else { if (embeddedViewStart(1)) { elementStart(0, 'div', null, [OtherDir]); - { listener('change', ctx.onChange.bind(ctx)); } + { + listener('change', function() { ctx.onChange(); }); + } elementEnd(); } embeddedViewEnd();