2

How to make change detection in unit tests work properly? From sources, changeDetection should be ran after microtasks are empty (including event tasks?).

this._onMicrotaskEmptySubscription = ngZone.onMicrotaskEmpty.subscribe({
    next: () => {
        if (this._autoDetect) {
            // Do a change detection run with checkNoChanges set to true to check
            // there are no changes on the second run.
            this.detectChanges(true);
        }
    }
});

In this short example, change detection ran after setTimeout, but not after manually clicking on the element. Is there a proper way to trigger change detection after event dispatching (inside fakeAsync zone, without fixture.detectChanges because in this case change detection will not be the same as in real life)?

import {
    fakeAsync, tick, ComponentFixture, TestBed, ComponentFixtureAutoDetect
} from '@angular/core/testing';
import {
    Component, QueryList, ViewChildren
} from '@angular/core';
import { By } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
describe('bug', () => {
    let host: Host;
    let fixture: ComponentFixture<Host>;
    @Component({
        selector: 'child',
        template: ``,
    })
    class Child {}
    @Component({
        template: `
            <ng-container *ngFor="let show of shows">
                <child *ngIf="show"></child>
            </ng-container>
            <button (click)="shows[1] = true">show</button>`
    })
    class Host {
        shows = [false, false];
        @ViewChildren(Child) children: QueryList<Child>;
        constructor() {
            setTimeout(() => this.shows[0] = true, 50);
        }
    }
    fit('test', fakeAsync(() => {
        TestBed.configureTestingModule({
            imports: [
                CommonModule,
            ],
            declarations: [
                Host, Child,
            ],
            providers: [{
                provide: ComponentFixtureAutoDetect,
                useValue: true,
            }]
        });
        fixture = TestBed.createComponent(Host);
        host = fixture.componentInstance;
        tick(10);
        expect(host.children.length).toEqual(0);
        tick(50);
        expect(host.children.length).toEqual(1);
        const button = fixture.debugElement.query(By.css('button'));
        button.triggerEventHandler('click', new Event('click'));
        tick(50);
        // fixture.detectChanges();
        expect(host.children.length).toEqual(2); // fails here
    }));
});
Ilia Volk
  • 526
  • 3
  • 9

1 Answers1

1

I found the solution to my question. HTMLElement.dispatchEvent should be used. How to trigger input, keyup events:

const inputDe = this.de.query(By.css('input'));
const inputEl = inputDe.nativeElement;
inputEl.value = text;
inputEl.focus(); // if it has matAutocompleteTrigger value accessor
inputEl.dispatchEvent(new Event('input'));
inputEl.dispatchEvent(new KeyboardEvent('keyup', {
      key: 'Enter',
}));

Full example

import {
    fakeAsync, tick, ComponentFixture, TestBed, ComponentFixtureAutoDetect
} from '@angular/core/testing';
import {
    Component, QueryList, ViewChildren
} from '@angular/core';
import { By } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
fdescribe('bug', () => {
    let host: Host;
    let fixture: ComponentFixture<Host>;
    @Component({
        selector: 'child',
        template: ``,
    })
    class Child {}
    @Component({
        template: `
            <ng-container *ngFor="let show of shows">
                <child *ngIf="show"></child>
            </ng-container>
            <button (click)="shows[1] = true">show</button>`
    })
    class Host {
        shows = [false, false];
        @ViewChildren(Child) children: QueryList<Child>;
        constructor() {
            setTimeout(() => this.shows[0] = true, 50);
        }
    }
    it('test', fakeAsync(() => {
        TestBed.configureTestingModule({
            imports: [
                CommonModule,
            ],
            declarations: [
                Host, Child,
            ],
            providers: [{
                provide: ComponentFixtureAutoDetect,
                useValue: true,
            }]
        });
        fixture = TestBed.createComponent(Host);
        host = fixture.componentInstance;
        tick(10);
        expect(host.children.length).toEqual(0);
        tick(50);
        expect(host.children.length).toEqual(1);
        const button = fixture.debugElement.query(By.css('button'));
        button.nativeElement.dispatchEvent(new Event('click')); // proper way
        tick(50);
        expect(host.children.length).toEqual(2); // no fail now
    }));
});
Ilia Volk
  • 526
  • 3
  • 9