fix(ivy): events should not mark views dirty by default (#25969)
PR Close #25969
This commit is contained in:
parent
21e566d9bc
commit
5653874683
|
@ -29,7 +29,7 @@ export const MONKEY_PATCH_KEY_NAME = '__ngContext__';
|
||||||
* of the context.
|
* of the context.
|
||||||
*/
|
*/
|
||||||
export interface LContext {
|
export interface LContext {
|
||||||
/** The component\'s view data */
|
/** The component's parent view data */
|
||||||
lViewData: LViewData;
|
lViewData: LViewData;
|
||||||
|
|
||||||
/** The index instance of the LNode */
|
/** The index instance of the LNode */
|
||||||
|
|
|
@ -1234,11 +1234,10 @@ export function listener(
|
||||||
// In order to match current behavior, native DOM event listeners must be added for all
|
// In order to match current behavior, native DOM event listeners must be added for all
|
||||||
// events (including outputs).
|
// events (including outputs).
|
||||||
if (isProceduralRenderer(renderer)) {
|
if (isProceduralRenderer(renderer)) {
|
||||||
const wrappedListener = wrapListenerWithDirtyLogic(viewData, listenerFn);
|
const cleanupFn = renderer.listen(node.native, eventName, listenerFn);
|
||||||
const cleanupFn = renderer.listen(node.native, eventName, wrappedListener);
|
|
||||||
storeCleanupFn(viewData, cleanupFn);
|
storeCleanupFn(viewData, cleanupFn);
|
||||||
} else {
|
} else {
|
||||||
const wrappedListener = wrapListenerWithDirtyAndDefault(viewData, listenerFn);
|
const wrappedListener = wrapListenerWithPreventDefault(listenerFn);
|
||||||
node.native.addEventListener(eventName, wrappedListener, useCapture);
|
node.native.addEventListener(eventName, wrappedListener, useCapture);
|
||||||
const cleanupInstances = getCleanup(viewData);
|
const cleanupInstances = getCleanup(viewData);
|
||||||
cleanupInstances.push(wrappedListener);
|
cleanupInstances.push(wrappedListener);
|
||||||
|
@ -2331,26 +2330,9 @@ export function markDirtyIfOnPush(node: LElementNode): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Wraps an event listener with preventDefault behavior. */
|
||||||
* Wraps an event listener so its host view and its ancestor views will be marked dirty
|
export function wrapListenerWithPreventDefault(listenerFn: (e?: any) => any): EventListener {
|
||||||
* whenever the event fires. Necessary to support OnPush components.
|
return function wrapListenerIn_preventDefault(e: Event) {
|
||||||
*/
|
|
||||||
export function wrapListenerWithDirtyLogic(
|
|
||||||
view: LViewData, listenerFn: (e?: any) => any): (e: Event) => any {
|
|
||||||
return function(e: any) {
|
|
||||||
markViewDirty(view);
|
|
||||||
return listenerFn(e);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps an event listener so its host view and its ancestor views will be marked dirty
|
|
||||||
* whenever the event fires. Also wraps with preventDefault behavior.
|
|
||||||
*/
|
|
||||||
export function wrapListenerWithDirtyAndDefault(
|
|
||||||
view: LViewData, listenerFn: (e?: any) => any): EventListener {
|
|
||||||
return function wrapListenerIn_markViewDirty(e: Event) {
|
|
||||||
markViewDirty(view);
|
|
||||||
if (listenerFn(e) === false) {
|
if (listenerFn(e) === false) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// Necessary for legacy browsers that don't support preventDefault (e.g. IE)
|
// Necessary for legacy browsers that don't support preventDefault (e.g. IE)
|
||||||
|
@ -2548,8 +2530,8 @@ function updateViewQuery<T>(viewQuery: ComponentQuery<{}>| null, component: T):
|
||||||
*/
|
*/
|
||||||
export function markDirty<T>(component: T) {
|
export function markDirty<T>(component: T) {
|
||||||
ngDevMode && assertDefined(component, 'component');
|
ngDevMode && assertDefined(component, 'component');
|
||||||
const lViewData = readPatchedLViewData(component) !;
|
const elementNode = getLElementFromComponent(component) !;
|
||||||
markViewDirty(lViewData);
|
markViewDirty(elementNode.data as LViewData);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////
|
///////////////////////////////
|
||||||
|
|
|
@ -812,6 +812,9 @@
|
||||||
{
|
{
|
||||||
"name": "makeParamDecorator"
|
"name": "makeParamDecorator"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "markDirty"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "markDirtyIfOnPush"
|
"name": "markDirtyIfOnPush"
|
||||||
},
|
},
|
||||||
|
@ -1023,9 +1026,6 @@
|
||||||
"name": "walkUpViews"
|
"name": "walkUpViews"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "wrapListenerWithDirtyAndDefault"
|
"name": "wrapListenerWithPreventDefault"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "wrapListenerWithDirtyLogic"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -9,7 +9,7 @@
|
||||||
import '@angular/core/test/bundling/util/src/reflect_metadata';
|
import '@angular/core/test/bundling/util/src/reflect_metadata';
|
||||||
|
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {Component, Injectable, NgModule, ViewEncapsulation, ɵrenderComponent as renderComponent} from '@angular/core';
|
import {Component, Injectable, NgModule, ViewEncapsulation, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core';
|
||||||
|
|
||||||
class Todo {
|
class Todo {
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
|
@ -66,13 +66,13 @@ class TodoStore {
|
||||||
<h1>todos</h1>
|
<h1>todos</h1>
|
||||||
<input class="new-todo" placeholder="What needs to be done?" autofocus=""
|
<input class="new-todo" placeholder="What needs to be done?" autofocus=""
|
||||||
[value]="newTodoText"
|
[value]="newTodoText"
|
||||||
(keyup)="$event.code == 'Enter' ? addTodo() : newTodoText = $event.target.value">
|
(keyup)="$event.code == 'Enter' ? addTodo() : updateNewTodoValue($event.target.value)">
|
||||||
</header>
|
</header>
|
||||||
<section *ngIf="todoStore.todos.length > 0" class="main">
|
<section *ngIf="todoStore.todos.length > 0" class="main">
|
||||||
<input *ngIf="todoStore.todos.length"
|
<input *ngIf="todoStore.todos.length"
|
||||||
#toggleall class="toggle-all" type="checkbox"
|
#toggleall class="toggle-all" type="checkbox"
|
||||||
[checked]="todoStore.allCompleted()"
|
[checked]="todoStore.allCompleted()"
|
||||||
(click)="todoStore.setAllTo(toggleall.checked)">
|
(click)="toggleAllTodos(toggleall.checked)">
|
||||||
<ul class="todo-list">
|
<ul class="todo-list">
|
||||||
<li *ngFor="let todo of todoStore.todos"
|
<li *ngFor="let todo of todoStore.todos"
|
||||||
[class.completed]="todo.completed"
|
[class.completed]="todo.completed"
|
||||||
|
@ -87,8 +87,8 @@ class TodoStore {
|
||||||
<input *ngIf="todo.editing"
|
<input *ngIf="todo.editing"
|
||||||
class="edit" #editedtodo
|
class="edit" #editedtodo
|
||||||
[value]="todo.title"
|
[value]="todo.title"
|
||||||
(blur)="stopEditing(todo, editedtodo.value)"
|
(blur)="updateEditingTodo(todo, editedtodo.value)"
|
||||||
(keyup)="todo.title = $event.target.value"
|
(keyup)="updateEditedTodoValue($event.target.value)"
|
||||||
(keyup)="$event.code == 'Enter' && updateEditingTodo(todo, editedtodo.value)"
|
(keyup)="$event.code == 'Enter' && updateEditingTodo(todo, editedtodo.value)"
|
||||||
(keyup)="$event.code == 'Escape' && cancelEditingTodo(todo)">
|
(keyup)="$event.code == 'Escape' && cancelEditingTodo(todo)">
|
||||||
</li>
|
</li>
|
||||||
|
@ -115,37 +115,63 @@ class ToDoAppComponent {
|
||||||
|
|
||||||
constructor(public todoStore: TodoStore) {}
|
constructor(public todoStore: TodoStore) {}
|
||||||
|
|
||||||
stopEditing(todo: Todo, editedTitle: string) {
|
cancelEditingTodo(todo: Todo) {
|
||||||
todo.title = editedTitle;
|
|
||||||
todo.editing = false;
|
todo.editing = false;
|
||||||
|
markDirty(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelEditingTodo(todo: Todo) { todo.editing = false; }
|
finishUpdatingTodo(todo: Todo, editedTitle: string) {
|
||||||
|
|
||||||
updateEditingTodo(todo: Todo, editedTitle: string) {
|
|
||||||
editedTitle = editedTitle.trim();
|
editedTitle = editedTitle.trim();
|
||||||
todo.editing = false;
|
|
||||||
|
|
||||||
if (editedTitle.length === 0) {
|
if (editedTitle.length === 0) {
|
||||||
return this.todoStore.remove(todo);
|
this.remove(todo);
|
||||||
}
|
}
|
||||||
|
|
||||||
todo.title = editedTitle;
|
todo.title = editedTitle;
|
||||||
|
this.cancelEditingTodo(todo);
|
||||||
}
|
}
|
||||||
|
|
||||||
editTodo(todo: Todo) { todo.editing = true; }
|
editTodo(todo: Todo) {
|
||||||
|
todo.editing = true;
|
||||||
|
markDirty(this);
|
||||||
|
}
|
||||||
|
|
||||||
removeCompleted() { this.todoStore.removeCompleted(); }
|
removeCompleted() {
|
||||||
|
this.todoStore.removeCompleted();
|
||||||
|
markDirty(this);
|
||||||
|
}
|
||||||
|
|
||||||
toggleCompletion(todo: Todo) { this.todoStore.toggleCompletion(todo); }
|
toggleCompletion(todo: Todo) {
|
||||||
|
this.todoStore.toggleCompletion(todo);
|
||||||
|
markDirty(this);
|
||||||
|
}
|
||||||
|
|
||||||
remove(todo: Todo) { this.todoStore.remove(todo); }
|
remove(todo: Todo) {
|
||||||
|
this.todoStore.remove(todo);
|
||||||
|
markDirty(this);
|
||||||
|
}
|
||||||
|
|
||||||
addTodo() {
|
addTodo() {
|
||||||
if (this.newTodoText.trim().length) {
|
if (this.newTodoText.trim().length) {
|
||||||
this.todoStore.add(this.newTodoText);
|
this.todoStore.add(this.newTodoText);
|
||||||
this.newTodoText = '';
|
this.newTodoText = '';
|
||||||
}
|
}
|
||||||
|
markDirty(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAllTodos(checked: boolean) {
|
||||||
|
this.todoStore.setAllTo(checked);
|
||||||
|
markDirty(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEditedTodoValue(todo: Todo, value: string) {
|
||||||
|
todo.title = value;
|
||||||
|
markDirty(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateNewTodoValue(value: string) {
|
||||||
|
this.newTodoText = value;
|
||||||
|
markDirty(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2466,10 +2466,7 @@
|
||||||
"name": "weekGetter"
|
"name": "weekGetter"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "wrapListenerWithDirtyAndDefault"
|
"name": "wrapListenerWithPreventDefault"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "wrapListenerWithDirtyLogic"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "wtfCreateScope"
|
"name": "wtfCreateScope"
|
||||||
|
|
|
@ -192,18 +192,31 @@ describe('change detection', () => {
|
||||||
expect(getRenderedText(myApp)).toEqual('3 - George');
|
expect(getRenderedText(myApp)).toEqual('3 - George');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should check OnPush components in update mode when component events occur', () => {
|
it('should not check OnPush components in update mode when component events occur, unless marked dirty',
|
||||||
const myApp = renderComponent(MyApp);
|
() => {
|
||||||
expect(getRenderedText(myApp)).toEqual('1 - Nancy');
|
const myApp = renderComponent(MyApp);
|
||||||
|
expect(comp.doCheckCount).toEqual(1);
|
||||||
|
expect(getRenderedText(myApp)).toEqual('1 - Nancy');
|
||||||
|
|
||||||
const button = containerEl.querySelector('button') !;
|
const button = containerEl.querySelector('button') !;
|
||||||
button.click();
|
button.click();
|
||||||
requestAnimationFrame.flush();
|
requestAnimationFrame.flush();
|
||||||
expect(getRenderedText(myApp)).toEqual('2 - Nancy');
|
// No ticks should have been scheduled.
|
||||||
|
expect(comp.doCheckCount).toEqual(1);
|
||||||
|
expect(getRenderedText(myApp)).toEqual('1 - Nancy');
|
||||||
|
|
||||||
tick(myApp);
|
tick(myApp);
|
||||||
expect(getRenderedText(myApp)).toEqual('2 - Nancy');
|
// The comp should still be clean. So doCheck will run, but the view should display 1.
|
||||||
});
|
expect(comp.doCheckCount).toEqual(2);
|
||||||
|
expect(getRenderedText(myApp)).toEqual('1 - Nancy');
|
||||||
|
|
||||||
|
markDirty(comp);
|
||||||
|
requestAnimationFrame.flush();
|
||||||
|
// Now that markDirty has been manually called, the view should be dirty and a tick
|
||||||
|
// should be scheduled to check the view.
|
||||||
|
expect(comp.doCheckCount).toEqual(3);
|
||||||
|
expect(getRenderedText(myApp)).toEqual('3 - Nancy');
|
||||||
|
});
|
||||||
|
|
||||||
it('should not check OnPush components in update mode when parent events occur', () => {
|
it('should not check OnPush components in update mode when parent events occur', () => {
|
||||||
function noop() {}
|
function noop() {}
|
||||||
|
@ -222,62 +235,78 @@ describe('change detection', () => {
|
||||||
|
|
||||||
const button = containerEl.querySelector('button#parent') !;
|
const button = containerEl.querySelector('button#parent') !;
|
||||||
(button as HTMLButtonElement).click();
|
(button as HTMLButtonElement).click();
|
||||||
requestAnimationFrame.flush();
|
tick(buttonParent);
|
||||||
|
// The comp should still be clean. So doCheck will run, but the view should display 1.
|
||||||
expect(getRenderedText(buttonParent)).toEqual('1 - Nancy');
|
expect(getRenderedText(buttonParent)).toEqual('1 - Nancy');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should check parent OnPush components in update mode when child events occur', () => {
|
it('should not check parent OnPush components in update mode when child events occur, unless marked dirty',
|
||||||
let parent: ButtonParent;
|
() => {
|
||||||
|
let parent: ButtonParent;
|
||||||
|
|
||||||
class ButtonParent implements DoCheck {
|
class ButtonParent implements DoCheck {
|
||||||
doCheckCount = 0;
|
doCheckCount = 0;
|
||||||
ngDoCheck(): void { this.doCheckCount++; }
|
ngDoCheck(): void { this.doCheckCount++; }
|
||||||
|
|
||||||
static ngComponentDef = defineComponent({
|
static ngComponentDef = defineComponent({
|
||||||
type: ButtonParent,
|
type: ButtonParent,
|
||||||
selectors: [['button-parent']],
|
selectors: [['button-parent']],
|
||||||
factory: () => parent = new ButtonParent(),
|
factory: () => parent = new ButtonParent(),
|
||||||
consts: 2,
|
consts: 2,
|
||||||
vars: 1,
|
vars: 1,
|
||||||
/** {{ doCheckCount }} - <my-comp></my-comp> */
|
/** {{ doCheckCount }} - <my-comp></my-comp> */
|
||||||
template: (rf: RenderFlags, ctx: ButtonParent) => {
|
template: (rf: RenderFlags, ctx: ButtonParent) => {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
text(0);
|
text(0);
|
||||||
element(1, 'my-comp');
|
element(1, 'my-comp');
|
||||||
}
|
}
|
||||||
if (rf & RenderFlags.Update) {
|
if (rf & RenderFlags.Update) {
|
||||||
textBinding(0, interpolation1('', ctx.doCheckCount, ' - '));
|
textBinding(0, interpolation1('', ctx.doCheckCount, ' - '));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
directives: () => [MyComponent],
|
directives: () => [MyComponent],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const MyButtonApp = createComponent('my-button-app', function(rf: RenderFlags, ctx: any) {
|
const MyButtonApp = createComponent('my-button-app', function(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
element(0, 'button-parent');
|
element(0, 'button-parent');
|
||||||
}
|
}
|
||||||
}, 1, 0, [ButtonParent]);
|
}, 1, 0, [ButtonParent]);
|
||||||
|
|
||||||
const myButtonApp = renderComponent(MyButtonApp);
|
const myButtonApp = renderComponent(MyButtonApp);
|
||||||
expect(parent !.doCheckCount).toEqual(1);
|
expect(parent !.doCheckCount).toEqual(1);
|
||||||
expect(comp !.doCheckCount).toEqual(1);
|
expect(comp !.doCheckCount).toEqual(1);
|
||||||
expect(getRenderedText(myButtonApp)).toEqual('1 - 1 - Nancy');
|
expect(getRenderedText(myButtonApp)).toEqual('1 - 1 - Nancy');
|
||||||
|
|
||||||
tick(myButtonApp);
|
tick(myButtonApp);
|
||||||
expect(parent !.doCheckCount).toEqual(2);
|
expect(parent !.doCheckCount).toEqual(2);
|
||||||
// parent isn't checked, so child doCheck won't run
|
// parent isn't checked, so child doCheck won't run
|
||||||
expect(comp !.doCheckCount).toEqual(1);
|
expect(comp !.doCheckCount).toEqual(1);
|
||||||
expect(getRenderedText(myButtonApp)).toEqual('1 - 1 - Nancy');
|
expect(getRenderedText(myButtonApp)).toEqual('1 - 1 - Nancy');
|
||||||
|
|
||||||
const button = containerEl.querySelector('button');
|
const button = containerEl.querySelector('button');
|
||||||
button !.click();
|
button !.click();
|
||||||
requestAnimationFrame.flush();
|
requestAnimationFrame.flush();
|
||||||
expect(parent !.doCheckCount).toEqual(3);
|
// No ticks should have been scheduled.
|
||||||
expect(comp !.doCheckCount).toEqual(2);
|
expect(parent !.doCheckCount).toEqual(2);
|
||||||
expect(getRenderedText(myButtonApp)).toEqual('3 - 2 - Nancy');
|
expect(comp !.doCheckCount).toEqual(1);
|
||||||
});
|
|
||||||
|
tick(myButtonApp);
|
||||||
|
expect(parent !.doCheckCount).toEqual(3);
|
||||||
|
// parent isn't checked, so child doCheck won't run
|
||||||
|
expect(comp !.doCheckCount).toEqual(1);
|
||||||
|
expect(getRenderedText(myButtonApp)).toEqual('1 - 1 - Nancy');
|
||||||
|
|
||||||
|
markDirty(comp);
|
||||||
|
requestAnimationFrame.flush();
|
||||||
|
// Now that markDirty has been manually called, both views should be dirty and a tick
|
||||||
|
// should be scheduled to check the view.
|
||||||
|
expect(parent !.doCheckCount).toEqual(4);
|
||||||
|
expect(comp !.doCheckCount).toEqual(2);
|
||||||
|
expect(getRenderedText(myButtonApp)).toEqual('4 - 2 - Nancy');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ChangeDetectorRef', () => {
|
describe('ChangeDetectorRef', () => {
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {defineComponent, defineDirective} from '../../src/render3/index';
|
import {bind, defineComponent, defineDirective, markDirty, textBinding} from '../../src/render3/index';
|
||||||
import {container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, listener, text} from '../../src/render3/instructions';
|
import {container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, listener, text} from '../../src/render3/instructions';
|
||||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||||
|
|
||||||
import {getRendererFactory2} from './imported_renderer2';
|
import {getRendererFactory2} from './imported_renderer2';
|
||||||
import {ComponentFixture, containerEl, renderComponent, renderToHtml} from './render_util';
|
import {ComponentFixture, containerEl, renderComponent, renderToHtml, requestAnimationFrame} from './render_util';
|
||||||
|
|
||||||
|
|
||||||
describe('event listeners', () => {
|
describe('event listeners', () => {
|
||||||
|
@ -363,6 +363,7 @@ describe('event listeners', () => {
|
||||||
/**
|
/**
|
||||||
* % for (let i = 0; i < ctx.buttons; i++) {
|
* % for (let i = 0; i < ctx.buttons; i++) {
|
||||||
* <button (click)="onClick(i)"> Click me </button>
|
* <button (click)="onClick(i)"> Click me </button>
|
||||||
|
* {{ counters[i] }}
|
||||||
* % }
|
* % }
|
||||||
*/
|
*/
|
||||||
class AppComp {
|
class AppComp {
|
||||||
|
@ -385,13 +386,20 @@ describe('event listeners', () => {
|
||||||
containerRefreshStart(0);
|
containerRefreshStart(0);
|
||||||
{
|
{
|
||||||
for (let i = 0; i < ctx.buttons; i++) {
|
for (let i = 0; i < ctx.buttons; i++) {
|
||||||
if (embeddedViewStart(1, 2, 0)) {
|
const rf1 = embeddedViewStart(1, 4, 1);
|
||||||
|
if (rf1 & RenderFlags.Create) {
|
||||||
elementStart(0, 'button');
|
elementStart(0, 'button');
|
||||||
{
|
{
|
||||||
listener('click', function() { return ctx.onClick(i); });
|
listener('click', function() { return ctx.onClick(i); });
|
||||||
text(1, 'Click me');
|
text(1, 'Click me');
|
||||||
}
|
}
|
||||||
elementEnd();
|
elementEnd();
|
||||||
|
elementStart(2, 'div');
|
||||||
|
{ text(3); }
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
if (rf1 & RenderFlags.Update) {
|
||||||
|
textBinding(3, bind(ctx.counters[i]));
|
||||||
}
|
}
|
||||||
embeddedViewEnd();
|
embeddedViewEnd();
|
||||||
}
|
}
|
||||||
|
@ -405,12 +413,27 @@ describe('event listeners', () => {
|
||||||
const fixture = new ComponentFixture(AppComp, {rendererFactory: getRendererFactory2(document)});
|
const fixture = new ComponentFixture(AppComp, {rendererFactory: getRendererFactory2(document)});
|
||||||
const comp = fixture.component;
|
const comp = fixture.component;
|
||||||
const buttons = fixture.hostElement.querySelectorAll('button') !;
|
const buttons = fixture.hostElement.querySelectorAll('button') !;
|
||||||
|
const divs = fixture.hostElement.querySelectorAll('div');
|
||||||
|
|
||||||
buttons[0].click();
|
buttons[0].click();
|
||||||
expect(comp.counters).toEqual([1, 0]);
|
expect(comp.counters).toEqual([1, 0]);
|
||||||
|
expect(divs[0].textContent).toEqual('0');
|
||||||
|
expect(divs[1].textContent).toEqual('0');
|
||||||
|
|
||||||
|
markDirty(comp);
|
||||||
|
requestAnimationFrame.flush();
|
||||||
|
expect(divs[0].textContent).toEqual('1');
|
||||||
|
expect(divs[1].textContent).toEqual('0');
|
||||||
|
|
||||||
buttons[1].click();
|
buttons[1].click();
|
||||||
expect(comp.counters).toEqual([1, 1]);
|
expect(comp.counters).toEqual([1, 1]);
|
||||||
|
expect(divs[0].textContent).toEqual('1');
|
||||||
|
expect(divs[1].textContent).toEqual('0');
|
||||||
|
|
||||||
|
markDirty(comp);
|
||||||
|
requestAnimationFrame.flush();
|
||||||
|
expect(divs[0].textContent).toEqual('1');
|
||||||
|
expect(divs[1].textContent).toEqual('1');
|
||||||
|
|
||||||
// the listener should be removed when the view is removed
|
// the listener should be removed when the view is removed
|
||||||
comp.buttons = 0;
|
comp.buttons = 0;
|
||||||
|
|
Loading…
Reference in New Issue