diff --git a/packages/ember-glimmer/tests/integration/components/target-action-test.js b/packages/ember-glimmer/tests/integration/components/target-action-test.js index 3dbb035622c..40bb54ca229 100644 --- a/packages/ember-glimmer/tests/integration/components/target-action-test.js +++ b/packages/ember-glimmer/tests/integration/components/target-action-test.js @@ -259,6 +259,36 @@ moduleFor('Components test: sendAction', class extends RenderingTest { this.runTask(() => innerChild.sendAction('bar', 'something special')); } + + ['@test asserts if called on a destroyed component']() { + let component; + + this.registerComponent('rip-alley', { + ComponentClass: Component.extend({ + init() { + this._super(); + component = this; + }, + + toString() { + return 'component:rip-alley'; + } + }) + }); + + this.render('{{#if shouldRender}}{{rip-alley}}{{/if}}', { shouldRender: true }); + + this.runTask(() => { + set(this.context, 'shouldRender', false); + }); + + expectAssertion( + () => { + component.sendAction('trigger-me-dead'); + }, + "Attempted to call .sendAction() with the action 'trigger-me-dead' on the destroyed object 'component:rip-alley'." + ); + } }); moduleFor('Components test: sendAction to a controller', class extends ApplicationTest { @@ -617,4 +647,34 @@ moduleFor('Components test: send', class extends RenderingTest { actions: ['foo'] }); } + + ['@test asserts if called on a destroyed component']() { + let component; + + this.registerComponent('rip-alley', { + ComponentClass: Component.extend({ + init() { + this._super(); + component = this; + }, + + toString() { + return 'component:rip-alley'; + } + }) + }); + + this.render('{{#if shouldRender}}{{rip-alley}}{{/if}}', { shouldRender: true }); + + this.runTask(() => { + set(this.context, 'shouldRender', false); + }); + + expectAssertion( + () => { + component.send('trigger-me-dead'); + }, + "Attempted to call .send() with the action 'trigger-me-dead' on the destroyed object 'component:rip-alley'." + ); + } }); diff --git a/packages/ember-routing/lib/system/route.js b/packages/ember-routing/lib/system/route.js index c3a533876dc..164c636d92a 100644 --- a/packages/ember-routing/lib/system/route.js +++ b/packages/ember-routing/lib/system/route.js @@ -1272,6 +1272,10 @@ let Route = EmberObject.extend(ActionHandler, Evented, { @public */ send(...args) { + assert( + `Attempted to call .send() with the action '${args[0]}' on the destroyed route '${this.routeName}'.`, + !this.isDestroying && !this.isDestroyed + ); if ((this._router && this._router._routerMicrolib) || !isTesting()) { this._router.send(...args); } else { diff --git a/packages/ember-routing/tests/system/route_test.js b/packages/ember-routing/tests/system/route_test.js index b6a93bea79c..9551f6c012a 100644 --- a/packages/ember-routing/tests/system/route_test.js +++ b/packages/ember-routing/tests/system/route_test.js @@ -193,6 +193,18 @@ moduleFor('Route', class extends AbstractTestCase { assert.equal(false, route.send('returnsFalse')); assert.equal(undefined, route.send('nonexistent', 1, 2, 3)); } + + ['@test .send asserts if called on a destroyed route']() { + route.routeName = 'rip-alley'; + runDestroy(route); + + expectAssertion( + () => { + route.send('trigger-me-dead'); + }, + "Attempted to call .send() with the action 'trigger-me-dead' on the destroyed route 'rip-alley'." + ); + } }); moduleFor('Route serialize', class extends AbstractTestCase { diff --git a/packages/ember-runtime/lib/mixins/action_handler.js b/packages/ember-runtime/lib/mixins/action_handler.js index 56baa9750b9..339f93859f4 100644 --- a/packages/ember-runtime/lib/mixins/action_handler.js +++ b/packages/ember-runtime/lib/mixins/action_handler.js @@ -197,6 +197,10 @@ const ActionHandler = Mixin.create({ @public */ send(actionName, ...args) { + assert( + `Attempted to call .send() with the action '${actionName}' on the destroyed object '${this}'.`, + !this.isDestroying && !this.isDestroyed + ); if (this.actions && this.actions[actionName]) { let shouldBubble = this.actions[actionName].apply(this, args) === true; if (!shouldBubble) { return; } diff --git a/packages/ember-runtime/tests/controllers/controller_test.js b/packages/ember-runtime/tests/controllers/controller_test.js index fc6972a208e..61422e59bb4 100644 --- a/packages/ember-runtime/tests/controllers/controller_test.js +++ b/packages/ember-runtime/tests/controllers/controller_test.js @@ -5,7 +5,7 @@ import Service from '../../system/service'; import { Mixin, get } from 'ember-metal'; import EmberObject from '../../system/object'; import inject from '../../inject'; -import { buildOwner } from 'internal-test-helpers'; +import { runDestroy, buildOwner } from 'internal-test-helpers'; QUnit.module('Controller event handling'); @@ -82,6 +82,26 @@ QUnit.test('Action can be handled by a superclass\' actions object', function(as controller.send('baz'); }); +QUnit.test('.send asserts if called on a destroyed controller', function() { + let owner = buildOwner(); + + owner.register('controller:application', Controller.extend({ + toString() { + return 'controller:rip-alley'; + } + })); + + let controller = owner.lookup('controller:application'); + runDestroy(owner); + + expectAssertion( + () => { + controller.send('trigger-me-dead'); + }, + "Attempted to call .send() with the action 'trigger-me-dead' on the destroyed object 'controller:rip-alley'." + ); +}); + QUnit.module('Controller deprecations'); QUnit.module('Controller Content -> Model Alias'); diff --git a/packages/ember-views/lib/mixins/action_support.js b/packages/ember-views/lib/mixins/action_support.js index 0ad1aa5fbdb..27daa327d59 100644 --- a/packages/ember-views/lib/mixins/action_support.js +++ b/packages/ember-views/lib/mixins/action_support.js @@ -109,6 +109,11 @@ export default Mixin.create({ @public */ sendAction(action, ...contexts) { + assert( + `Attempted to call .sendAction() with the action '${action}' on the destroyed object '${this}'.`, + !this.isDestroying && !this.isDestroyed + ); + let actionName; // Send the default action @@ -132,6 +137,11 @@ export default Mixin.create({ }, send(actionName, ...args) { + assert( + `Attempted to call .send() with the action '${actionName}' on the destroyed object '${this}'.`, + !this.isDestroying && !this.isDestroyed + ); + let action = this.actions && this.actions[actionName]; if (action) {