Skip to content

ui.router.state.$state.$get() creates a pending task that affects $timeout.verifyNoPendingTasks() #3365

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1 of 3 tasks
scottohara opened this issue Mar 12, 2017 · 2 comments

Comments

@scottohara
Copy link

This is a:

  • Bug Report
  • Feature Request
  • General Query

My version of UI-Router is: (version)
0.4.2

Bug Report

Current Behavior:

There is an open angular issue (angular/angular.js#14336) where $timeout.verifyNoPendingTasks() throws if there are deferred functions that are unrelated to $timeout.

When upgrading my app from angular 1.6.1 to 1.6.2, one of my unit tests started failing with:

Error: Deferred tasks to flush (1): {id: 0, time: 0}
    at Function.$delegate.verifyNoPendingTasks (https://code.angularjs.org/1.6.2/angular-mocks.js:2153:13)

"What does this have to do with ui.router?", you ask?

I was able to trace the source of the deferred task back to ui.router.state.$state.$get()

It seems that the $get() function for the $stateProvider immediately creates a rejected promise (TransitionSuperseded), which is later used if a state being transitioned to is no longer valid:

function $get($rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) {
    var TransitionSupersededError = new Error('transition superseded');
    var TransitionSuperseded = silenceUncaughtInPromise($q.reject(TransitionSupersededError));
    ...
}

The call to $q.reject() here creates a pending task, i.e. in angular:

qFactory.reject() -> qFactory.rejectPromise() -> qFactory.$$reject() -> scheduleProcessQueue(promise.$$state)

scheduleProcessQueue(state) checks if state.status === 2, and if so calls nextTick(processChecks) -> $rootScope.$evalAsync() -> $browser.defer(), which pushes an item onto $browser.deferredFns making it’s length > 0...hence a pending task.

Expected Behavior:

Although the real issue at the root of this is angular/angular.js#14336 -the fact that $timeout.verifyNoPendingTasks() throws when there are any pending tasks (not just $timeout related ones); is it correct that simply loading the ui.router module (without any states configured, and no transitions triggered) causes an item to be added to $browser.deferredFns?

A workaround to fix my failing test was to simply call $timout.flush() to ensure that there are no pending tasks before running test that checks $timeout.verifyNoPendingTasks(); but the question remains: is it right that ui.router creates a pending task just in case a state transition is later superseded?

Link to Plunker that reproduces the issue:

https://plnkr.co/edit/BlRThFUSqWL0eGTvfFaA?p=preview

(uncomment the $timout.flush() call to apply the workaround)

@christopherthielen
Copy link
Contributor

christopherthielen commented Mar 25, 2017

I appreciate the detailed and well researched bug report, thank you.

I don't think building the rejection beforehand is necessarily an anti-pattern, but I also do not think that it is strictly necessary. If you create a PR for legacy branch which generates those rejections on the fly using a factory, I will merge it.

I'd potentially need the same PR also created against the master branch, if we're doing the same thing in 1.0 (we might not be).

@scottohara
Copy link
Author

I have recently updated from v0.4.2 to v1.0.5, and I am happy to report that this no longer appears to be an issue (and I was able to remove the superfluous $timeout.flush() call that was previously added).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants