Search code examples
angulartestingjestjsintegration-testing

Angular testing why is flush necessary when clicking but not in triggerEventHandler


I have an Angular module with a list, card, detail, and edit components.

The list template would look like this:

<own-list>
  <button [routerLink]="['/', 'mod', 'create']" data-testid="new-request">Create</button>
  <own-card *ngFor="let elem of elements" [routerLink]="['/', 'mod', elem.id]"></own-card>
<own-list>

When testing that when clicking the card it goes to its detail page:

  test('it should navigate to detail when clicking a card', fakeAsync(() => {
    const cardElems = rootFixture.debugElement.queryAll(By.css('own-card'));
    // Navigate to top card detail
    cardElems[0].nativeElement.click();
    tick();
    fixture.detectChanges();
    // Check route is correct
    expect(location.path()).toBe(`/mod/${sortedElements[0].id}`);
  }));

works fine.

When testing that when clicking the create button goes to edit:

  test('it should navigate to edit when clicking new request', fakeAsync(() => {
    const debugCreateBtn = rootFixture.debugElement.query(By.css(`[data-testid="new-request"]`));
    // Navigate to edit
    debugCreateBtn.nativeElement.click();
    tick();
    fixture.detectChanges();
    // Check route is correct
    expect(location.path()).toBe(`/mod/create`);
  }));

it fails with 1 periodic timer(s) still in the queue.

If I add flush as such:

  test('it should navigate to edit when clicking new request', fakeAsync(() => {
    const debugCreateBtn = rootFixture.debugElement.query(By.css(`[data-testid="new-request"]`));
    // Navigate to edit
    debugCreateBtn.nativeElement.click();
    tick();
    fixture.detectChanges();
    flush();                   // <-- new flush
    // Check route is correct
    expect(location.path()).toBe(`/mod/create`);
  }));

it works well.

And if I switch to triggerEventHandler:

  test('it should navigate to edit when clicking new request', fakeAsync(() => {
    const debugCreateBtn = rootFixture.debugElement.query(By.css(`[data-testid="new-request"]`));
    // Navigate to edit
    // Run in ngZone to avoid warning
    fixture.ngZone.run(() => debugCreateBtn.triggerEventHandler('click', { button: 0 }));
    tick();
    fixture.detectChanges();
    // Check route is correct
    expect(location.path()).toBe(`/mod/create`);
  }));

then it works, without the flush.

Could someone explain why it works or doesn't in each situation?


Solution

  • flushMicrotasks flushes pending microtasks, tick flushes pending microtasks and progresses time, flush flushes pending microtasks, macrotasks and progresses time, all within the context of fakeAsync.

    Native events like click are macrotasks. triggerEventHandler and EventEmitters without async set to true use microtasks.