angular: [beta.16 regression/testing] Scheduler / tick not working for code with multiple async-events

IMPORTANT: This repository’s issues are reserved for feature requests and bug reports. Do not submit support requests here, see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question.

This is pretty hard to describe, but I’m basically testing the typical Typeahead-Example everybody demonstrates. So this is my controller-code:

  searchTerm = new Control();
  ngOnInit() {
      this.searchTerm.valueChanges
        .do((val)=> console.log("Before debounce", val))
        .debounceTime(200)
        .do((val)=> console.log("After Debounce", val))
        .switchMap((term :string) => { return this.taskService.findTasks({'q': term})})
        .subscribe(() => {
        });
  }

and this is my test:

  iit('should execute a Typeahead-Search', fakeAsync(() => {
    fixture.detectChanges();
    let taskList = fixture.componentInstance,
      element = fixture.nativeElement;

    var spy : Spy = spyOn(taskService, 'findTasks').and.callThrough();
    let input = element.querySelector('#searchField');
    taskList.searchTerm.updateValue("Term");
    tick(500);
    console.log("Input Value: ", input.value); // prints 'Input Value: ', 'Term' correctly
    expect(taskService.findTasks).toHaveBeenCalledWith({'q': 'Term'});
  }));

this worked perfectly fine in beta.15 but fails with beta.16 / zone0.6.12. The error message is:

Error: 1 timer(s) still in the queue.

After some debugging I think the problematic line is this one:

https://github.com/angular/zone.js/blob/master/lib/zone-spec/fake-async-test.ts#L23

As for every scheduled task the member-variable _currentTime is increased by the passed delay. In my example the scheduleFunction is executed twice:

First time for the valueChanges with delay (0) and second time for the debounceTime with delay(200). As in the mean-time there was also a call to tick(500) the endTime sums up to 700 which will noch be reached in the test. Changing the mentioned line to:

    var endTime = delay;

lets my test work properly. But I’m not sure if this maybe leads to other problems…

Current behavior

multiple async functions (valueChanges + debounceTime) break test because of buggy scheduler behaviour

Expected/desired behavior

multiple async functions should not break the scheduler

Other information

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 18
  • Comments: 16 (6 by maintainers)

Commits related to this issue

Most upvoted comments

this is still happening in v4.

@nethulap

Your discardPeriodicTasks() tip helped me. I was dispatching KeyUp events, which would work on the first tick(500) but give the “Error: 1 periodic timers(s) still in queue” the second time I tried to dispatch same event.

Code excerpt

    @HostListener('keyup') private onKeyUp(): void {
        console.log('keyup');
        this.activityStream.next(true);
    }

    this.activityStream
        .do(() => this.setActive()) // this will change is active class
        .debounceTime(500)
        .subscribe(() => this.setIdle());

Test excerpt

    it('should remove the is idle class if a user starts typing again', fakeAsync(() => {
        const {input, fixture} = setup();

        input.dispatchEvent(new Event('keyup'));
        tick(500);
        fixture.detectChanges();
        expect(input.classList.contains('is-idle')).toBe(true);
        expect(input.classList.contains('has-idled')).toBe(true);
        input.dispatchEvent(new Event('keyup'));
        
       discardPeriodicTasks(); **<- YOU NEED THIS**

        tick(100);
        fixture.detectChanges();

        // this is checking that is-idle will be false if we keyup 
        // without hitting another 500ms
        expect(input.classList.contains('is-idle')).toBe(false);
        expect(input.classList.contains('has-idled')).toBe(true);
    }));

for a quick fix for this issue call discardPeriodicTasks() at the end.

ticking twice works for me

tick(); //Needed to get around issue: https://github.com/angular/angular/issues/8251
tick(delay);`

I’d experienced many errors like Error: 1 timer(s) still in the queue. as well (before beta 16); my hacky workaround had been to add tick(1000); statements.

I was able to reproduce the error way easier. The problem is when an async-event triggers another async-event (in case of the typeahead it’s the async “valueChanges”-execution that triggers the debounceTime). The following test also fails with: Error: 1 timer(s) still in the queue.

  it('tick should simulate a given time period', fakeAsync(() => {
    let flag = false;
    setTimeout(() => {
      Observable.timer(500).subscribe(() => flag = true);
    });
    tick(800);
    expect(flag).toBeTruthy();
  }));

In fake-async-test.js the calculated endTime is 1300:

Scheduler.prototype.tick = function (millis) {
    if (millis === void 0) { millis = 0; }
        this._currentTime += millis;
        while (this._schedulerQueue.length > 0) {
            var current = this._schedulerQueue[0];
            console.log("_currentTime", this._currentTime); // PRINTS 800 in the last execution
            console.log("endTime", current.endTime); // PRINTS 1300 in the last execution
            if (this._currentTime < current.endTime) {
                // Done processing the queue since it's sorted by endTime.
                break;
                }
                else {
                    // Time to run scheduled function. Remove it from the head of queue.
                    var current_1 = this._schedulerQueue.shift();
                    var retval = current_1.func.apply(global, current_1.args);
                    if (!retval) {
                            // Uncaught exception in the current scheduled function. Stop processing the queue.
                            break;
                    }
                }
        }
};