diff --git a/addon-test-support/@ember/test-helpers/settled.ts b/addon-test-support/@ember/test-helpers/settled.ts index 2c39e4d44..a645d0db8 100644 --- a/addon-test-support/@ember/test-helpers/settled.ts +++ b/addon-test-support/@ember/test-helpers/settled.ts @@ -4,9 +4,9 @@ import jQuery from 'jquery'; import Ember from 'ember'; -import { getContext } from './setup-context'; import { nextTick } from './-utils'; import waitUntil from './wait-until'; +import { hasPendingTransitions } from './setup-application-context'; // Ember internally tracks AJAX requests in the same way that we do here for // legacy style "acceptance" tests using the `ember-testing.js` asset provided @@ -43,23 +43,6 @@ function pendingRequests() { return localRequestsPending + internalRequestsPending; } -/** - @private - @returns {boolean} if there are any pending router transitions -*/ -function hasPendingTransitions() { - const context = getContext(); - const owner = context && context.owner; - - if (!owner) { - return false; - } - - const router = owner.lookup('router:main'); - const isLoading = router._routerMicrolib && !!router._routerMicrolib.activeTransition; - return isLoading; -} - /** @private @param {Event} event (unused) @@ -166,7 +149,7 @@ export interface SettledState { hasPendingTimers: boolean; hasPendingWaiters: boolean; hasPendingRequests: boolean; - hasPendingTransitions: boolean; + hasPendingTransitions: boolean | null; pendingRequestCount: number; } diff --git a/addon-test-support/@ember/test-helpers/setup-application-context.ts b/addon-test-support/@ember/test-helpers/setup-application-context.ts index 7a602ecfe..24c4de645 100644 --- a/addon-test-support/@ember/test-helpers/setup-application-context.ts +++ b/addon-test-support/@ember/test-helpers/setup-application-context.ts @@ -9,11 +9,61 @@ export interface ApplicationTestContext extends TestContext { element?: Element | null; } +const CAN_USE_ROUTER_EVENTS = hasEmberVersion(3, 6); +let routerTransitionsPending = false; +const ROUTER = new WeakMap(); + // eslint-disable-next-line require-jsdoc export function isApplicationTestContext(context: BaseContext): context is ApplicationTestContext { return isTestContext(context); } +/** + Resets the flag determining if transitions are pending (used by hasPendingTransitions) + + @public + @returns {undefined} +*/ +export function resetRouterTransitionPendingState() { + routerTransitionsPending = false; +} + +/** + Determines if we have any pending router transtions (used to determine `settled` state) + + @public + @returns {(boolean|null)} if there are pending transitions +*/ +export function hasPendingTransitions(): boolean | null { + if (CAN_USE_ROUTER_EVENTS) { + return routerTransitionsPending; + } + + let context = getContext(); + + // there is no current context, we cannot check + if (context === undefined) { + return null; + } + + let router = ROUTER.get(context); + + if (router === undefined) { + // if there is no router (e.g. no `visit` calls made yet), we cannot + // check for pending transitions but this is explicitly not an error + // condition + return null; + } + + let routerMicrolib = router._routerMicrolib || router.router; + + if (routerMicrolib === undefined) { + return null; + } + + return !!routerMicrolib.activeTransition; +} + /** Navigate the application to the provided URL. @@ -32,7 +82,22 @@ export function visit(url: string, options?: { [key: string]: any }): Promise { - return owner.visit(url, options); + let visitResult = owner.visit(url, options); + + if (CAN_USE_ROUTER_EVENTS) { + let router = owner.lookup('service:router'); + + // track pending transitions via the public routeWillChange / routeDidChange APIs + // routeWillChange can fire many times and is only useful to know when we have _started_ + // transitioning, we can then use routeDidChange to signal that the transition has settled + router.on('routeWillChange', () => (routerTransitionsPending = true)); + router.on('routeDidChange', () => (routerTransitionsPending = false)); + } else { + let router = owner.lookup('router:main'); + ROUTER.set(context, router); + } + + return visitResult; }) .then(() => { if (global.EmberENV._APPLICATION_TEMPLATE_WRAPPER !== false) { diff --git a/addon-test-support/@ember/test-helpers/teardown-application-context.ts b/addon-test-support/@ember/test-helpers/teardown-application-context.ts index 176ce5b03..d3f96d245 100644 --- a/addon-test-support/@ember/test-helpers/teardown-application-context.ts +++ b/addon-test-support/@ember/test-helpers/teardown-application-context.ts @@ -1,6 +1,6 @@ import { nextTickPromise } from './-utils'; import settled from './settled'; - +import { resetRouterTransitionPendingState } from './setup-application-context'; /** Used by test framework addons to tear down the provided context after testing is completed. @@ -11,6 +11,8 @@ import settled from './settled'; @returns {Promise} resolves when settled */ export default function(context: object, options?: { waitForSettled?: boolean }): Promise { + resetRouterTransitionPendingState(); + let waitForSettled = true; if (options !== undefined && 'waitForSettled' in options) { waitForSettled = options.waitForSettled!; diff --git a/tests/unit/settled-test.js b/tests/unit/settled-test.js index 713d59c18..efea6d3d0 100644 --- a/tests/unit/settled-test.js +++ b/tests/unit/settled-test.js @@ -1,6 +1,6 @@ import Ember from 'ember'; import { module, test } from 'qunit'; -import { isSettled, getSettledState, setContext, unsetContext } from '@ember/test-helpers'; +import { isSettled, getSettledState } from '@ember/test-helpers'; import hasEmberVersion from '@ember/test-helpers/has-ember-version'; import { _setupAJAXHooks, _teardownAJAXHooks } from '@ember/test-helpers/settled'; import { next, later, run, schedule } from '@ember/runloop'; @@ -26,7 +26,7 @@ module('settled', function(hooks) { hasPendingRequests: false, hasPendingTimers: false, hasPendingWaiters: false, - hasPendingTransitions: false, + hasPendingTransitions: null, hasRunLoop: false, pendingRequestCount: 0, }, @@ -145,32 +145,6 @@ module('settled', function(hooks) { assert.strictEqual(isSettled(), true, 'post cond'); }); - - test('when router is transitioning', async function(assert) { - assert.expect(3); - - setContext(this); - assert.strictEqual(isSettled(), true); - - this.owner = { - lookup: () => ({ - _routerMicrolib: { - activeTransition: {}, - }, - }), - }; - assert.strictEqual(isSettled(), false); - - this.owner = { - lookup: () => ({ - _routerMicrolib: { - activeTransition: null, - }, - }), - }; - assert.strictEqual(isSettled(), true); - unsetContext(); - }); }); module('getSettledState', function() { @@ -179,7 +153,7 @@ module('settled', function(hooks) { hasPendingRequests: false, hasPendingTimers: false, hasPendingWaiters: false, - hasPendingTransitions: false, + hasPendingTransitions: null, hasRunLoop: false, pendingRequestCount: 0, }); @@ -197,7 +171,7 @@ module('settled', function(hooks) { hasPendingRequests: false, hasPendingTimers: true, hasPendingWaiters: false, - hasPendingTransitions: false, + hasPendingTransitions: null, hasRunLoop: true, pendingRequestCount: 0, }); @@ -216,7 +190,7 @@ module('settled', function(hooks) { hasPendingRequests: false, hasPendingTimers: true, hasPendingWaiters: false, - hasPendingTransitions: false, + hasPendingTransitions: null, hasRunLoop: false, pendingRequestCount: 0, }); @@ -235,7 +209,7 @@ module('settled', function(hooks) { hasPendingRequests: false, hasPendingTimers: true, hasPendingWaiters: false, - hasPendingTransitions: false, + hasPendingTransitions: null, hasRunLoop: false, pendingRequestCount: 0, }); @@ -251,7 +225,7 @@ module('settled', function(hooks) { hasPendingRequests: false, hasPendingTimers: false, hasPendingWaiters: false, - hasPendingTransitions: false, + hasPendingTransitions: null, hasRunLoop: true, pendingRequestCount: 0, }); @@ -279,7 +253,7 @@ module('settled', function(hooks) { hasPendingRequests: true, hasPendingTimers: false, hasPendingWaiters: false, - hasPendingTransitions: false, + hasPendingTransitions: null, hasRunLoop: false, pendingRequestCount: 1, }); @@ -288,7 +262,7 @@ module('settled', function(hooks) { hasPendingRequests: false, hasPendingTimers: false, hasPendingWaiters: true, - hasPendingTransitions: false, + hasPendingTransitions: null, hasRunLoop: false, pendingRequestCount: 0, }); @@ -306,7 +280,7 @@ module('settled', function(hooks) { hasPendingRequests: false, hasPendingTimers: false, hasPendingWaiters: true, - hasPendingTransitions: false, + hasPendingTransitions: null, hasRunLoop: false, pendingRequestCount: 0, }); @@ -316,38 +290,6 @@ module('settled', function(hooks) { assert.strictEqual(isSettled(), true, 'post cond'); }); - test('when router is transitioning', function(assert) { - assert.expect(3); - assert.strictEqual(isSettled(), true); - - setContext(this); - this.owner = { - lookup: () => ({ - _routerMicrolib: { - activeTransition: {}, - }, - }), - }; - assert.deepEqual(getSettledState(), { - hasPendingRequests: false, - hasPendingTimers: false, - hasPendingWaiters: false, - hasPendingTransitions: true, - hasRunLoop: false, - pendingRequestCount: 0, - }); - - this.owner = { - lookup: () => ({ - _routerMicrolib: { - activeTransition: null, - }, - }), - }; - assert.strictEqual(isSettled(), true, 'post cond'); - unsetContext(); - }); - test('all the things!', function(assert) { assert.expect(6); let done = assert.async(); @@ -359,7 +301,7 @@ module('settled', function(hooks) { hasPendingRequests: false, hasPendingTimers: false, hasPendingWaiters: true, - hasPendingTransitions: false, + hasPendingTransitions: null, hasRunLoop: false, pendingRequestCount: 0, }); @@ -369,7 +311,7 @@ module('settled', function(hooks) { hasPendingRequests: false, hasPendingTimers: false, hasPendingWaiters: true, - hasPendingTransitions: false, + hasPendingTransitions: null, hasRunLoop: true, pendingRequestCount: 0, }); @@ -380,7 +322,7 @@ module('settled', function(hooks) { hasPendingRequests: false, hasPendingTimers: true, hasPendingWaiters: true, - hasPendingTransitions: false, + hasPendingTransitions: null, hasRunLoop: true, pendingRequestCount: 0, });