From ec24b705cc7e5218d33319d541a49c845f284347 Mon Sep 17 00:00:00 2001 From: Trent Willis Date: Fri, 28 Oct 2016 14:47:41 -0700 Subject: [PATCH 1/3] [CLEANUP] Add more generic functionality to test case classes --- .../lib/test-cases/abstract-application.js | 8 ++++++++ packages/internal-test-helpers/lib/test-cases/abstract.js | 4 ++++ .../internal-test-helpers/lib/test-cases/query-param.js | 8 -------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/internal-test-helpers/lib/test-cases/abstract-application.js b/packages/internal-test-helpers/lib/test-cases/abstract-application.js index 035d84d9b07..b26dfb62974 100644 --- a/packages/internal-test-helpers/lib/test-cases/abstract-application.js +++ b/packages/internal-test-helpers/lib/test-cases/abstract-application.js @@ -33,6 +33,10 @@ export default class AbstractApplicationTestCase extends AbstractTestCase { }; } + get appRouter() { + return this.applicationInstance.lookup('router:main'); + } + teardown() { if (this.applicationInstance) { runDestroy(this.applicationInstance); @@ -53,6 +57,10 @@ export default class AbstractApplicationTestCase extends AbstractTestCase { } } + transitionTo() { + return run(this.appRouter, 'transitionTo', ...arguments); + } + compile(string, options) { return compile(...arguments); } diff --git a/packages/internal-test-helpers/lib/test-cases/abstract.js b/packages/internal-test-helpers/lib/test-cases/abstract.js index d40c28adf95..48d3f92bf6b 100644 --- a/packages/internal-test-helpers/lib/test-cases/abstract.js +++ b/packages/internal-test-helpers/lib/test-cases/abstract.js @@ -39,6 +39,10 @@ export default class AbstractTestCase { run(callback); } + runTaskNext(callback) { + run.next(callback); + } + // The following methods require `this.element` to work get firstChild() { diff --git a/packages/internal-test-helpers/lib/test-cases/query-param.js b/packages/internal-test-helpers/lib/test-cases/query-param.js index 55ed888e596..987a405d0ca 100644 --- a/packages/internal-test-helpers/lib/test-cases/query-param.js +++ b/packages/internal-test-helpers/lib/test-cases/query-param.js @@ -54,10 +54,6 @@ export default class QueryParamTestCase extends ApplicationTestCase { return this.applicationInstance.lookup(`route:${name}`); } - get appRouter() { - return this.applicationInstance.lookup('router:main'); - } - get routerOptions() { return { location: 'test' @@ -72,10 +68,6 @@ export default class QueryParamTestCase extends ApplicationTestCase { this.assert.equal(this.appRouter.get('location.path'), path, message); } - transitionTo() { - return run(this.appRouter, 'transitionTo', ...arguments); - } - /** Sets up a Controller for a given route with a single query param and default value. Can optionally extend the controller with an object. From 8b054729cc2d082363881f49a1d5088c3320af0b Mon Sep 17 00:00:00 2001 From: Trent Willis Date: Fri, 28 Oct 2016 16:23:27 -0700 Subject: [PATCH 2/3] [BUGFIX beta] Improve test coverage for Engine loading/error routes --- .../integration/application/engine-test.js | 245 +++++++++++++++++- .../ember/tests/routing/substates_test.js | 107 +------- 2 files changed, 244 insertions(+), 108 deletions(-) diff --git a/packages/ember-glimmer/tests/integration/application/engine-test.js b/packages/ember-glimmer/tests/integration/application/engine-test.js index 4dece3fab01..2a5a8b25d86 100644 --- a/packages/ember-glimmer/tests/integration/application/engine-test.js +++ b/packages/ember-glimmer/tests/integration/application/engine-test.js @@ -1,19 +1,26 @@ import { moduleFor, ApplicationTest } from '../../utils/test-case'; import { strip } from '../../utils/abstract-test-case'; import { compile } from '../../utils/helpers'; -import { Controller } from 'ember-runtime'; +import { Controller, RSVP } from 'ember-runtime'; import { Component } from 'ember-glimmer'; import { Engine } from 'ember-application'; import { Route } from 'ember-routing'; moduleFor('Application test: engine rendering', class extends ApplicationTest { setupAppAndRoutableEngine(hooks = []) { + let self = this; + this.application.register('template:application', compile('Application{{outlet}}')); this.router.map(function() { this.mount('blog'); }); - this.application.register('route-map:blog', function() { }); + this.application.register('route-map:blog', function() { + this.route('post', function() { + this.route('comments'); + this.route('likes'); + }); + }); this.registerRoute('application', Route.extend({ model() { hooks.push('application - application'); @@ -33,6 +40,10 @@ moduleFor('Application test: engine rendering', class extends ApplicationTest { hooks.push('engine - application'); } })); + + if (self._additionalEngineRegistrations) { + self._additionalEngineRegistrations.call(this); + } } })); } @@ -108,6 +119,10 @@ moduleFor('Application test: engine rendering', class extends ApplicationTest { })); } + additionalEngineRegistrations(callback) { + this._additionalEngineRegistrations = callback; + } + setupEngineWithAttrs(hooks) { this.application.register('template:application', compile('Application{{mount "chat-engine"}}')); @@ -331,4 +346,230 @@ moduleFor('Application test: engine rendering', class extends ApplicationTest { this.assertText('ApplicationEngineEnglish'); }); } + + ['@test error substate route works for the application route of an Engine'](assert) { + assert.expect(2); + + this.setupAppAndRoutableEngine(); + this.application.__registry__.resolver.moduleBasedResolver = true; + this.additionalEngineRegistrations(function() { + this.register('template:application_error', compile('Error! {{model.message}}')); + this.register('route:post', Route.extend({ + model() { + return RSVP.reject(new Error('Oh, noes!')); + } + })); + }); + + return this.visit('/').then(() => { + this.assertText('Application'); + return this.transitionTo('blog.post'); + }).catch(() => { + this.assertText('ApplicationError! Oh, noes!'); + }); + } + + ['@test error route works for the application route of an Engine'](assert) { + assert.expect(2); + + this.setupAppAndRoutableEngine(); + this.application.__registry__.resolver.moduleBasedResolver = true; + this.additionalEngineRegistrations(function() { + this.register('template:error', compile('Error! {{model.message}}')); + this.register('route:post', Route.extend({ + model() { + return RSVP.reject(new Error('Oh, noes!')); + } + })); + }); + + return this.visit('/').then(() => { + this.assertText('Application'); + return this.transitionTo('blog.post'); + }).catch(() => { + this.assertText('ApplicationEngineError! Oh, noes!'); + }); + } + + ['@test error substate route works for a child route of an Engine'](assert) { + assert.expect(2); + + this.setupAppAndRoutableEngine(); + this.application.__registry__.resolver.moduleBasedResolver = true; + this.additionalEngineRegistrations(function() { + this.register('template:post_error', compile('Error! {{model.message}}')); + this.register('route:post', Route.extend({ + model() { + return RSVP.reject(new Error('Oh, noes!')); + } + })); + }); + + return this.visit('/').then(() => { + this.assertText('Application'); + return this.transitionTo('blog.post'); + }).catch(() => { + this.assertText('ApplicationEngineError! Oh, noes!'); + }); + } + + ['@test error route works for a child route of an Engine'](assert) { + assert.expect(2); + + this.setupAppAndRoutableEngine(); + this.application.__registry__.resolver.moduleBasedResolver = true; + this.additionalEngineRegistrations(function() { + this.register('template:post.error', compile('Error! {{model.message}}')); + this.register('route:post.comments', Route.extend({ + model() { + return RSVP.reject(new Error('Oh, noes!')); + } + })); + }); + + return this.visit('/').then(() => { + this.assertText('Application'); + return this.transitionTo('blog.post.comments'); + }).catch(() => { + this.assertText('ApplicationEngineError! Oh, noes!'); + }); + } + + ['@test loading substate route works for the application route of an Engine'](assert) { + assert.expect(3); + + let resolveLoading; + + this.setupAppAndRoutableEngine(); + this.application.__registry__.resolver.moduleBasedResolver = true; + this.additionalEngineRegistrations(function() { + this.register('template:application_loading', compile('Loading')); + this.register('template:post', compile('Post')); + this.register('route:post', Route.extend({ + model() { + return new RSVP.Promise((resolve) => { + resolveLoading = resolve; + }); + } + })); + }); + + return this.visit('/').then(() => { + this.assertText('Application'); + let transition = this.transitionTo('blog.post'); + + this.runTaskNext(() => { + this.assertText('ApplicationLoading'); + resolveLoading(); + }); + + return transition.then(() => { + this.runTaskNext(() => this.assertText('ApplicationEnginePost')); + }); + }); + } + + ['@test loading route works for the application route of an Engine'](assert) { + assert.expect(3); + + let resolveLoading; + + this.setupAppAndRoutableEngine(); + this.additionalEngineRegistrations(function() { + this.register('template:loading', compile('Loading')); + this.register('template:post', compile('Post')); + this.register('route:post', Route.extend({ + model() { + return new RSVP.Promise((resolve) => { + resolveLoading = resolve; + }); + } + })); + }); + + return this.visit('/').then(() => { + this.assertText('Application'); + let transition = this.transitionTo('blog.post'); + + this.runTaskNext(() => { + this.assertText('ApplicationEngineLoading'); + resolveLoading(); + }); + + return transition.then(() => { + this.runTaskNext(() => this.assertText('ApplicationEnginePost')); + }); + }); + } + + ['@test loading substate route works for a child route of an Engine'](assert) { + assert.expect(3); + + let resolveLoading; + + this.setupAppAndRoutableEngine(); + this.application.__registry__.resolver.moduleBasedResolver = true; + this.additionalEngineRegistrations(function() { + this.register('template:post', compile('{{outlet}}')); + this.register('template:post.comments', compile('Comments')); + this.register('template:post.likes_loading', compile('Loading')); + this.register('template:post.likes', compile('Likes')); + this.register('route:post.likes', Route.extend({ + model() { + return new RSVP.Promise((resolve) => { + resolveLoading = resolve; + }); + } + })); + }); + + return this.visit('/blog/post/comments').then(() => { + this.assertText('ApplicationEngineComments'); + let transition = this.transitionTo('blog.post.likes'); + + this.runTaskNext(() => { + this.assertText('ApplicationEngineLoading'); + resolveLoading(); + }); + + return transition.then(() => { + this.runTaskNext(() => this.assertText('ApplicationEngineLikes')); + }); + }); + } + + ['@test loading route works for a child route of an Engine'](assert) { + assert.expect(3); + + let resolveLoading; + + this.setupAppAndRoutableEngine(); + this.additionalEngineRegistrations(function() { + this.register('template:post', compile('{{outlet}}')); + this.register('template:post.comments', compile('Comments')); + this.register('template:post.loading', compile('Loading')); + this.register('template:post.likes', compile('Likes')); + this.register('route:post.likes', Route.extend({ + model() { + return new RSVP.Promise((resolve) => { + resolveLoading = resolve; + }); + } + })); + }); + + return this.visit('/blog/post/comments').then(() => { + this.assertText('ApplicationEngineComments'); + let transition = this.transitionTo('blog.post.likes'); + + this.runTaskNext(() => { + this.assertText('ApplicationEngineLoading'); + resolveLoading(); + }); + + return transition.then(() => { + this.runTaskNext(() => this.assertText('ApplicationEngineLikes')); + }); + }); + } }); diff --git a/packages/ember/tests/routing/substates_test.js b/packages/ember/tests/routing/substates_test.js index 59470e3b557..f09c94dbb7e 100644 --- a/packages/ember/tests/routing/substates_test.js +++ b/packages/ember/tests/routing/substates_test.js @@ -2,7 +2,7 @@ import { RSVP, Controller } from 'ember-runtime'; import { Route, NoneLocation } from 'ember-routing'; import { run } from 'ember-metal'; import { compile } from 'ember-template-compiler'; -import { Application, Engine, Resolver } from 'ember-application'; +import { Application, Resolver } from 'ember-application'; import { jQuery } from 'ember-views'; import { setTemplates, setTemplate } from 'ember-glimmer'; @@ -1046,108 +1046,3 @@ QUnit.test('Rejected promises returned from ApplicationRoute transition into top equal(jQuery('#app', '#qunit-fixture').text(), 'INDEX'); }); - -QUnit.test('Slow Promise from an Engine application route enters the mounts loading state', function() { - expect(1); - - templates['news/blog_loading'] = 'BLOG LOADING'; - - // Register engine - let BlogEngine = Engine.extend(); - registry.register('engine:blog', BlogEngine); - - // Register engine route map - let BlogMap = function() {}; - registry.register('route-map:blog', BlogMap); - - Router.map(function() { - this.route('news', function() { - this.mount('blog'); - }); - }); - - let deferred = RSVP.defer(); - let BlogRoute = Route.extend({ - model() { - return deferred.promise; - } - }); - - var blog = container.lookup('engine:blog'); - blog.register('route:application', BlogRoute); - - bootApplication('/news/blog'); - - equal(jQuery('#app', '#qunit-fixture').text(), 'BLOG LOADING', 'news/blog_loading was entered'); - - run(deferred, 'resolve'); -}); - -QUnit.test('Rejected Promise from an Engine application route enters the mounts error state', function() { - expect(1); - - templates['news/blog_error'] = 'BLOG ERROR'; - - // Register engine - let BlogEngine = Engine.extend(); - registry.register('engine:blog', BlogEngine); - - // Register engine route map - let BlogMap = function() {}; - registry.register('route-map:blog', BlogMap); - - Router.map(function() { - this.route('news', function() { - this.mount('blog'); - }); - }); - - let BlogRoute = Route.extend({ - model() { - return RSVP.Promise.reject(); - } - }); - - var blog = container.lookup('engine:blog'); - blog.register('route:application', BlogRoute); - - bootApplication('/news/blog'); - - equal(jQuery('#app', '#qunit-fixture').text(), 'BLOG ERROR', 'news/blog_loading was entered'); -}); - -QUnit.test('Slow Promise from an Engine application route enters the mounts loading state with resetNamespace', function() { - expect(1); - - templates['blog_loading'] = 'BLOG LOADING'; - - // Register engine - let BlogEngine = Engine.extend(); - registry.register('engine:blog', BlogEngine); - - // Register engine route map - let BlogMap = function() {}; - registry.register('route-map:blog', BlogMap); - - Router.map(function() { - this.route('news', function() { - this.mount('blog', { resetNamespace: true }); - }); - }); - - let deferred = RSVP.defer(); - let BlogRoute = Route.extend({ - model() { - return deferred.promise; - } - }); - - var blog = container.lookup('engine:blog'); - blog.register('route:application', BlogRoute); - - bootApplication('/news/blog'); - - equal(jQuery('#app', '#qunit-fixture').text(), 'BLOG LOADING', 'news/blog_loading was entered'); - - run(deferred, 'resolve'); -}); From abf1c9d3e6059c4a73958796741c7eaab72e0458 Mon Sep 17 00:00:00 2001 From: Trent Willis Date: Fri, 28 Oct 2016 16:23:49 -0700 Subject: [PATCH 3/3] [BUGFIX beta] Fix loading/error substates and routes for Engines --- packages/ember-routing/lib/system/dsl.js | 26 ++- packages/ember-routing/lib/system/router.js | 193 ++++++++++++-------- 2 files changed, 135 insertions(+), 84 deletions(-) diff --git a/packages/ember-routing/lib/system/dsl.js b/packages/ember-routing/lib/system/dsl.js index 722570845bc..a2492849ef4 100644 --- a/packages/ember-routing/lib/system/dsl.js +++ b/packages/ember-routing/lib/system/dsl.js @@ -166,6 +166,7 @@ DSL.prototype.mount = function(_name, _options) { } let callback; + let dummyErrorRoute = `/_unused_dummy_error_path_route_${name}/:error`; if (engineRouteMap) { let shouldResetEngineInfo = false; let oldEngineInfo = this.options.engineInfo; @@ -177,6 +178,9 @@ DSL.prototype.mount = function(_name, _options) { let optionsForChild = assign({ engineInfo }, this.options); let childDSL = new DSL(fullName, optionsForChild); + createRoute(childDSL, 'loading'); + createRoute(childDSL, 'error', { path: dummyErrorRoute }); + engineRouteMap.call(childDSL); callback = childDSL.generate(); @@ -186,15 +190,25 @@ DSL.prototype.mount = function(_name, _options) { } } - if (this.enableLoadingSubstates) { - let dummyErrorRoute = `/_unused_dummy_error_path_route_${name}/:error`; - createRoute(this, `${name}_loading`, { resetNamespace: options.resetNamespace }); - createRoute(this, `${name}_error`, { resetNamespace: options.resetNamespace, path: dummyErrorRoute }); - } - let localFullName = 'application'; let routeInfo = assign({ localFullName }, engineInfo); + if (this.enableLoadingSubstates) { + // These values are important to register the loading routes under their + // proper names for the Router and within the Engine's registry. + let substateName = `${name}_loading`; + let localFullName = `application_loading`; + let routeInfo = assign({ localFullName }, engineInfo); + createRoute(this, substateName, { resetNamespace: options.resetNamespace }); + this.options.addRouteForEngine(substateName, routeInfo); + + substateName = `${name}_error`; + localFullName = `application_error`; + routeInfo = assign({ localFullName }, engineInfo); + createRoute(this, substateName, { resetNamespace: options.resetNamespace, path: dummyErrorRoute }); + this.options.addRouteForEngine(substateName, routeInfo); + } + this.options.addRouteForEngine(fullName, routeInfo); this.push(path, fullName, callback); diff --git a/packages/ember-routing/lib/system/router.js b/packages/ember-routing/lib/system/router.js index 14cc96ff35b..63c8825f4de 100644 --- a/packages/ember-routing/lib/system/router.js +++ b/packages/ember-routing/lib/system/router.js @@ -1036,35 +1036,39 @@ const EmberRouter = EmberObject.extend(Evented, { }); /* - Helper function for iterating root-ward, starting - from (but not including) the provided `originRoute`. + Helper function for iterating over routes in a set of handlerInfos that are + at or above the given origin route. Example: if `originRoute` === 'foo.bar' + and the handlerInfos given were for 'foo.bar.baz', then the given callback + will be invoked with the routes for 'foo.bar', 'foo', and 'application' + individually. - Returns true if the last callback fired requested - to bubble upward. + If the callback returns anything other than `true`, then iteration will stop. @private + @param {Route} originRoute + @param {Array} handlerInfos + @param {Function} callback + @return {Void} */ -function forEachRouteAbove(originRoute, transition, callback) { - let handlerInfos = transition.state.handlerInfos; +function forEachRouteAbove(originRoute, handlerInfos, callback) { let originRouteFound = false; - let handlerInfo, route; for (let i = handlerInfos.length - 1; i >= 0; --i) { - handlerInfo = handlerInfos[i]; - route = handlerInfo.handler; + let handlerInfo = handlerInfos[i]; + let route = handlerInfo.handler; + + if (originRoute === route) { + originRouteFound = true; + } if (!originRouteFound) { - if (originRoute === route) { - originRouteFound = true; - } continue; } - if (callback(route, handlerInfos[i + 1].handler) !== true) { - return false; + if (callback(route) !== true) { + return; } } - return true; } // These get invoked when an action bubbles above ApplicationRoute @@ -1075,55 +1079,63 @@ let defaultActionHandlers = { originRoute.router._scheduleLoadingEvent(transition, originRoute); }, + // Attempt to find an appropriate error route or substate to enter. error(error, transition, originRoute) { - // Attempt to find an appropriate error substate to enter. + let handlerInfos = transition.state.handlerInfos; let router = originRoute.router; - let tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) { - let childErrorRouteName = findChildRouteName(route, childRoute, 'error'); - if (childErrorRouteName) { - router.intermediateTransitionTo(childErrorRouteName, error); - return; + forEachRouteAbove(originRoute, handlerInfos, function(route) { + // Check for the existence of an 'error' route. + // We don't check for an 'error' route on the originRoute, since that would + // technically be below where we're at in the route hierarchy. + if (originRoute !== route) { + let errorRouteName = findRouteStateName(route, 'error'); + if (errorRouteName) { + router.intermediateTransitionTo(errorRouteName, error); + return false; + } } - return true; - }); - if (tryTopLevel) { - // Check for top-level error state to enter. - if (routeHasBeenDefined(originRoute.router, 'application_error')) { - router.intermediateTransitionTo('application_error', error); - return; + // Check for an 'error' substate route + let errorSubstateName = findRouteSubstateName(route, 'error'); + if (errorSubstateName) { + router.intermediateTransitionTo(errorSubstateName, error); + return false; } - } + + return true; + }); logError(error, 'Error while processing route: ' + transition.targetName); }, + // Attempt to find an appropriate loading route or substate to enter. loading(transition, originRoute) { - // Attempt to find an appropriate loading substate to enter. + let handlerInfos = transition.state.handlerInfos; let router = originRoute.router; - let tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) { - let childLoadingRouteName = findChildRouteName(route, childRoute, 'loading'); + forEachRouteAbove(originRoute, handlerInfos, function(route) { + // Check for the existence of a 'loading' route. + // We don't check for a 'loading' route on the originRoute, since that would + // technically be below where we're at in the route hierarchy. + if (originRoute !== route) { + let loadingRouteName = findRouteStateName(route, 'loading'); + if (loadingRouteName) { + router.intermediateTransitionTo(loadingRouteName); + return false; + } + } - if (childLoadingRouteName) { - router.intermediateTransitionTo(childLoadingRouteName); - return; + // Check for loading substate + let loadingSubstateName = findRouteSubstateName(route, 'loading'); + if (loadingSubstateName) { + router.intermediateTransitionTo(loadingSubstateName); + return false; } // Don't bubble above pivot route. - if (transition.pivotHandler !== route) { - return true; - } + return transition.pivotHandler !== route; }); - - if (tryTopLevel) { - // Check for top-level loading state to enter. - if (routeHasBeenDefined(originRoute.router, 'application_loading')) { - router.intermediateTransitionTo('application_loading'); - return; - } - } } }; @@ -1148,45 +1160,70 @@ function logError(_error, initialMessage) { Logger.error.apply(this, errorArgs); } -function findChildRouteName(parentRoute, originatingChildRoute, name) { - let router = parentRoute.router; - let childName; - let originatingChildRouteName = originatingChildRoute.routeName; +/** + Finds the name of the substate route if it exists for the given route. A + substate route is of the form `route_state`, such as `foo_loading`. - // The only time the originatingChildRoute's name should be 'application' - // is if we're entering an engine - if (originatingChildRouteName === 'application') { - originatingChildRouteName = getOwner(originatingChildRoute).mountPoint; - } + @private + @param {Route} route + @param {String} state + @return {String} +*/ +function findRouteSubstateName(route, state) { + let router = route.router; + let owner = getOwner(route); - // First, try a named loading state of the route, e.g. 'foo_loading' - childName = originatingChildRouteName + '_' + name; - if (routeHasBeenDefined(router, childName)) { - return childName; - } + let routeName = route.routeName; + let substateName = routeName + '_' + state; - // Second, try general loading state of the parent, e.g. 'loading' - let originatingChildRouteParts = originatingChildRouteName.split('.').slice(0, -1); - let namespace; + let routeNameFull = route.fullRouteName; + let substateNameFull = routeNameFull + '_' + state; - // If there is a namespace on the route, then we use that, otherwise we use - // the parent route as the namespace. - if (originatingChildRouteParts.length) { - namespace = originatingChildRouteParts.join('.') + '.'; - } else { - namespace = parentRoute.routeName === 'application' ? '' : parentRoute.routeName + '.'; - } + return routeHasBeenDefined(owner, router, substateName, substateNameFull) ? + substateNameFull : + ''; +} - childName = namespace + name; - if (routeHasBeenDefined(router, childName)) { - return childName; - } +/** + Finds the name of the state route if it exists for the given route. A state + route is of the form `route.state`, such as `foo.loading`. Properly Handles + `application` named routes. + + @private + @param {Route} route + @param {String} state + @return {String} +*/ +function findRouteStateName(route, state) { + let router = route.router; + let owner = getOwner(route); + + let routeName = route.routeName; + let stateName = routeName === 'application' ? state : routeName + '.' + state; + + let routeNameFull = route.fullRouteName; + let stateNameFull = routeNameFull === 'application' ? state : routeNameFull + '.' + state; + + return routeHasBeenDefined(owner, router, stateName, stateNameFull) ? + stateNameFull : + ''; } -function routeHasBeenDefined(router, name) { - let owner = getOwner(router); - return router.hasRoute(name) && - (owner.hasRegistration(`template:${name}`) || owner.hasRegistration(`route:${name}`)); +/** + Determines whether or not a route has been defined by checking that the route + is in the Router's map and the owner has a registration for that route. + + @private + @param {Owner} owner + @param {Ember.Router} router + @param {String} localName + @param {String} fullName + @return {Boolean} +*/ +function routeHasBeenDefined(owner, router, localName, fullName) { + let routerHasRoute = router.hasRoute(fullName); + let ownerHasRoute = owner.hasRegistration(`template:${localName}`) || owner.hasRegistration(`route:${localName}`); + return routerHasRoute && ownerHasRoute; } export function triggerEvent(handlerInfos, ignoreFailure, args) {