diff --git a/packages/@ember/-internals/glimmer/lib/components/abstract-input.ts b/packages/@ember/-internals/glimmer/lib/components/abstract-input.ts index 659837f22b2..c81f4b23565 100644 --- a/packages/@ember/-internals/glimmer/lib/components/abstract-input.ts +++ b/packages/@ember/-internals/glimmer/lib/components/abstract-input.ts @@ -2,8 +2,8 @@ import { tracked } from '@ember/-internals/metal'; import { TargetActionSupport } from '@ember/-internals/runtime'; import { TextSupport } from '@ember/-internals/views'; import { EMBER_MODERNIZED_BUILT_IN_COMPONENTS } from '@ember/canary-features'; -import { assert, deprecate } from '@ember/debug'; -import { JQUERY_INTEGRATION, SEND_ACTION } from '@ember/deprecated-features'; +import { assert } from '@ember/debug'; +import { JQUERY_INTEGRATION } from '@ember/deprecated-features'; import { action } from '@ember/object'; import { isConstRef, isUpdatableRef, Reference, updateRef, valueForRef } from '@glimmer/reference'; import Component from '../component'; @@ -207,86 +207,6 @@ export function handleDeprecatedFeatures( target: InternalComponentConstructor, attributeBindings: Array ): void { - if (SEND_ACTION) { - let angle = target.toString(); - let { prototype } = target; - - interface View { - send(action: string, ...args: unknown[]): void; - } - - let isView = (target: {}): target is View => { - return typeof (target as Partial).send === 'function'; - }; - - let superListenerFor = prototype['listenerFor']; - - Object.defineProperty(prototype, 'listenerFor', { - configurable: true, - enumerable: false, - value: function listenerFor(this: AbstractInput, name: string): EventListener { - const actionName = this.named(name); - - if (typeof actionName === 'string') { - deprecate( - `Passing actions to components as strings (like \`<${angle} @${name}="${actionName}" />\`) is deprecated. ` + - `Please use closure actions instead (\`<${angle} @${name}={{action "${actionName}"}} />\`).`, - false, - { - id: 'ember-component.send-action', - for: 'ember-source', - since: {}, - until: '4.0.0', - url: 'https://deprecations.emberjs.com/v3.x#toc_ember-component-send-action', - } - ); - - const { caller } = this; - - assert('[BUG] missing caller', caller && typeof caller === 'object'); - - let listener: Function; - - if (isView(caller)) { - listener = (...args: unknown[]) => caller.send(actionName, ...args); - } else { - assert( - `The action '${actionName}' did not exist on ${caller}`, - typeof caller[actionName] === 'function' - ); - - listener = caller[actionName]; - } - - let deprecatedListener = (...args: unknown[]) => { - deprecate( - `Passing actions to components as strings (like \`<${angle} @${name}="${actionName}" />\`) is deprecated. ` + - `Please use closure actions instead (\`<${angle} @${name}={{action "${actionName}"}} />\`).`, - false, - { - id: 'ember-component.send-action', - for: 'ember-source', - since: {}, - until: '4.0.0', - url: 'https://deprecations.emberjs.com/v3.x#toc_ember-component-send-action', - } - ); - - return listener(...args); - }; - - if (this.isVirtualEventListener(name, deprecatedListener)) { - return devirtualize(deprecatedListener); - } else { - return deprecatedListener as EventListener; - } - } else { - return superListenerFor.call(this, name); - } - }, - }); - } - if (EMBER_MODERNIZED_BUILT_IN_COMPONENTS) { let { prototype } = target; diff --git a/packages/@ember/-internals/glimmer/lib/helpers/action.ts b/packages/@ember/-internals/glimmer/lib/helpers/action.ts index 4999a46d81c..2a2315b5b6d 100644 --- a/packages/@ember/-internals/glimmer/lib/helpers/action.ts +++ b/packages/@ember/-internals/glimmer/lib/helpers/action.ts @@ -95,10 +95,10 @@ export const ACTIONS = new _WeakSet(); Closure actions curry both their scope and any arguments. When invoked, any additional arguments are added to the already curried list. - Actions should be invoked using the [sendAction](/ember/release/classes/Component/methods/sendAction?anchor=sendAction) - method. The first argument to `sendAction` is the action to be called, and - additional arguments are passed to the action function. This has interesting - properties combined with currying of arguments. For example: + Actions are presented in JavaScript as callbacks, and are + invoked like any other JavaScript function. + + For example ```app/components/update-name.js import Component from '@glimmer/component'; @@ -152,7 +152,7 @@ export const ACTIONS = new _WeakSet(); export default Component.extend({ click() { // Note that model is not passed, it was curried in the template - this.sendAction('submit', 'bob'); + this.submit('bob'); } }); ``` diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/input-angle-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/input-angle-test.js index 905498e9f86..4657de9ddd9 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/input-angle-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/input-angle-test.js @@ -554,31 +554,6 @@ moduleFor( // this.assertSelectionRange(8, 8); //NOTE: this fails in IE, the range is 0 -> 0 (TEST_SUITE=sauce) } - ['@test [DEPRECATED] sends an action with `` when is pressed']( - assert - ) { - assert.expect(4); - - expectDeprecation(() => { - this.render(``, { - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - if (jQueryDisabled) { - assert.notOk(event.originalEvent, 'event is not a jQuery.Event'); - } else { - assert.ok(event instanceof jQuery.Event, 'jQuery event was passed'); - } - }, - }, - }); - }, 'Passing actions to components as strings (like ``) is deprecated. Please use closure actions instead (``). (\'-top-level\' @ L1:C0) '); - - expectDeprecation(() => { - this.triggerEvent('keyup', { key: 'Enter' }); - }, 'Passing actions to components as strings (like ``) is deprecated. Please use closure actions instead (``).'); - } - ['@test sends an action with `` when is pressed']( assert ) { @@ -602,31 +577,6 @@ moduleFor( }); } - ['@test [DEPRECATED] sends an action with `` is pressed'](assert) { - assert.expect(4); - - expectDeprecation(() => { - this.render(``, { - value: 'initial', - - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - if (jQueryDisabled) { - assert.notOk(event.originalEvent, 'event is not a jQuery.Event'); - } else { - assert.ok(event instanceof jQuery.Event, 'jQuery event was passed'); - } - }, - }, - }); - }, /Passing actions to components as strings \(like `({{input key-press="foo"}}|)`\) is deprecated\.|Passing the `@key-press` argument to is deprecated\./); - - expectDeprecation(() => { - this.triggerEvent('keypress', { key: 'A' }); - }, /Passing actions to components as strings \(like `({{input key-press="foo"}}|)`\) is deprecated\./); - } - ['@test sends an action with `` is pressed'](assert) { let triggered = 0; @@ -727,31 +677,6 @@ moduleFor( }); } - ['@test [DEPRECATED] sends an action with `` when is pressed']( - assert - ) { - assert.expect(4); - - expectDeprecation(() => { - this.render(``, { - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - if (jQueryDisabled) { - assert.notOk(event.originalEvent, 'event is not a jQuery.Event'); - } else { - assert.ok(event instanceof jQuery.Event, 'jQuery event was passed'); - } - }, - }, - }); - }, 'Passing actions to components as strings (like ``) is deprecated. Please use closure actions instead (``). (\'-top-level\' @ L1:C0) '); - - expectDeprecation(() => { - this.triggerEvent('keyup', { key: 'Escape' }); - }, 'Passing actions to components as strings (like ``) is deprecated. Please use closure actions instead (``).'); - } - ['@test sends an action with `` when is pressed']( assert ) { @@ -773,31 +698,6 @@ moduleFor( this.triggerEvent('keyup', { key: 'Escape' }); } - ['@test [DEPRECATED] sends an action with `` when a key is pressed']( - assert - ) { - assert.expect(4); - - expectDeprecation(() => { - this.render(``, { - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - if (jQueryDisabled) { - assert.notOk(event.originalEvent, 'event is not a jQuery.Event'); - } else { - assert.ok(event instanceof jQuery.Event, 'jQuery event was passed'); - } - }, - }, - }); - }, /Passing actions to components as strings \(like `({{input key-down="foo"}}|)`\) is deprecated\.|Passing the `@key-down` argument to is deprecated\./); - - expectDeprecation(() => { - this.triggerEvent('keydown', { key: 'A' }); - }, 'Passing actions to components as strings (like ``) is deprecated. Please use closure actions instead (``).'); - } - ['@test [DEPRECATED] sends an action with `` when a key is pressed']( assert ) { @@ -828,31 +728,6 @@ moduleFor( assert.equal(triggered, 1, 'The action was triggered exactly once'); } - ['@test [DEPRECATED] sends an action with `` when a key is pressed']( - assert - ) { - assert.expect(4); - - expectDeprecation(() => { - this.render(``, { - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - if (jQueryDisabled) { - assert.notOk(event.originalEvent, 'event is not a jQuery.Event'); - } else { - assert.ok(event instanceof jQuery.Event, 'jQuery event was passed'); - } - }, - }, - }); - }, /Passing actions to components as strings \(like `({{input key-up="foo"}}|)`\) is deprecated\.|Passing the `@key-up` argument to is deprecated\./); - - expectDeprecation(() => { - this.triggerEvent('keyup', { key: 'A' }); - }, 'Passing actions to components as strings (like ``) is deprecated. Please use closure actions instead (``).'); - } - ['@test [DEPRECATED] sends an action with `` when a key is pressed']( assert ) { diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/input-curly-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/input-curly-test.js index f32278a6d56..589649eecd4 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/input-curly-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/input-curly-test.js @@ -385,31 +385,6 @@ moduleFor( // this.assertSelectionRange(8, 8); //NOTE: this fails in IE, the range is 0 -> 0 (TEST_SUITE=sauce) } - ['@test [DEPRECATED] sends an action with `{{input enter="foo"}}` when is pressed']( - assert - ) { - assert.expect(4); - - expectDeprecation(() => { - this.render(`{{input enter='foo'}}`, { - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - if (jQueryDisabled) { - assert.notOk(event.originalEvent, 'event is not a jQuery.Event'); - } else { - assert.ok(event instanceof jQuery.Event, 'jQuery event was passed'); - } - }, - }, - }); - }, 'Passing actions to components as strings (like `{{input enter="foo"}}`) is deprecated. Please use closure actions instead (`{{input enter=(action "foo")}}`). (\'-top-level\' @ L1:C0) '); - - expectDeprecation(() => { - this.triggerEvent('keyup', { key: 'Enter' }); - }, 'Passing actions to components as strings (like ``) is deprecated. Please use closure actions instead (``).'); - } - ['@test sends an action with `{{input enter=(action "foo")}}` when is pressed']( assert ) { @@ -433,31 +408,6 @@ moduleFor( }); } - ['@test [DEPRECATED] sends an action with `{{input key-press="foo"}}` is pressed'](assert) { - assert.expect(4); - - expectDeprecation(() => { - this.render(`{{input value=this.value key-press='foo'}}`, { - value: 'initial', - - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - if (jQueryDisabled) { - assert.notOk(event.originalEvent, 'event is not a jQuery.Event'); - } else { - assert.ok(event instanceof jQuery.Event, 'jQuery event was passed'); - } - }, - }, - }); - }, /Passing actions to components as strings \(like `({{input key-press="foo"}}|)`\) is deprecated\.|Passing the `@key-press` argument to is deprecated\./); - - expectDeprecation(() => { - this.triggerEvent('keypress', { key: 'A' }); - }, /Passing actions to components as strings \(like `({{input key-press="foo"}}|)`\) is deprecated\./); - } - ['@test sends an action with `{{input key-press=(action "foo")}}` is pressed'](assert) { let triggered = 0; @@ -558,31 +508,6 @@ moduleFor( }); } - ['@test [DEPRECATED] sends an action with `{{input escape-press="foo"}}` when is pressed']( - assert - ) { - assert.expect(4); - - expectDeprecation(() => { - this.render(`{{input escape-press='foo'}}`, { - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - if (jQueryDisabled) { - assert.notOk(event.originalEvent, 'event is not a jQuery.Event'); - } else { - assert.ok(event instanceof jQuery.Event, 'jQuery event was passed'); - } - }, - }, - }); - }, 'Passing actions to components as strings (like `{{input escape-press="foo"}}`) is deprecated. Please use closure actions instead (`{{input escape-press=(action "foo")}}`). (\'-top-level\' @ L1:C0) '); - - expectDeprecation(() => { - this.triggerEvent('keyup', { key: 'Escape' }); - }, 'Passing actions to components as strings (like ``) is deprecated. Please use closure actions instead (``).'); - } - ['@test sends an action with `{{input escape-press=(action "foo")}}` when is pressed']( assert ) { @@ -604,31 +529,6 @@ moduleFor( this.triggerEvent('keyup', { key: 'Escape' }); } - ['@test [DEPRECATED] sends an action with `{{input key-down="foo"}}` when a key is pressed']( - assert - ) { - assert.expect(4); - - expectDeprecation(() => { - this.render(`{{input key-down='foo'}}`, { - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - if (jQueryDisabled) { - assert.notOk(event.originalEvent, 'event is not a jQuery.Event'); - } else { - assert.ok(event instanceof jQuery.Event, 'jQuery event was passed'); - } - }, - }, - }); - }, /Passing actions to components as strings \(like `({{input key-down="foo"}}|)`\) is deprecated\.|Passing the `@key-down` argument to is deprecated\./); - - expectDeprecation(() => { - this.triggerEvent('keydown', { key: 'A' }); - }, 'Passing actions to components as strings (like ``) is deprecated. Please use closure actions instead (``).'); - } - ['@test sends an action with `{{input key-down=(action "foo")}}` when a key is pressed']( assert ) { @@ -659,31 +559,6 @@ moduleFor( assert.equal(triggered, 1, 'The action was triggered exactly once'); } - ['@test [DEPRECATED] sends an action with `{{input key-up="foo"}}` when a key is pressed']( - assert - ) { - assert.expect(4); - - expectDeprecation(() => { - this.render(`{{input key-up='foo'}}`, { - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - if (jQueryDisabled) { - assert.notOk(event.originalEvent, 'event is not a jQuery.Event'); - } else { - assert.ok(event instanceof jQuery.Event, 'jQuery event was passed'); - } - }, - }, - }); - }, /Passing actions to components as strings \(like `({{input key-up="foo"}}|)`\) is deprecated\.|Passing the `@key-up` argument to is deprecated\./); - - expectDeprecation(() => { - this.triggerEvent('keyup', { key: 'A' }); - }, 'Passing actions to components as strings (like ``) is deprecated. Please use closure actions instead (``).'); - } - ['@test sends an action with `{{input key-up=(action "foo")}}` when a key is pressed'](assert) { expectDeprecation( () => { diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/target-action-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/target-action-test.js index 6e991250fbc..e40ad1d65be 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/target-action-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/target-action-test.js @@ -1,604 +1,11 @@ -import { - moduleFor, - RenderingTestCase, - ApplicationTestCase, - strip, - runTask, -} from 'internal-test-helpers'; +import { moduleFor, RenderingTestCase, runTask } from 'internal-test-helpers'; import { set, Mixin } from '@ember/-internals/metal'; import Controller from '@ember/controller'; import { Object as EmberObject } from '@ember/-internals/runtime'; -import { Route } from '@ember/-internals/routing'; import { Component } from '../../utils/helpers'; -function expectSendActionDeprecation(fn) { - expectDeprecation( - fn, - /You called (.*).sendAction\((.*)\) but Component#sendAction is deprecated. Please use closure actions instead./ - ); -} - -moduleFor( - 'Components test: sendAction', - class extends RenderingTestCase { - constructor() { - super(...arguments); - this.actionCounts = {}; - this.sendCount = 0; - this.actionArguments = null; - - let self = this; - - this.registerComponent('action-delegate', { - ComponentClass: Component.extend({ - init() { - this._super(); - self.delegate = this; - this.name = 'action-delegate'; - }, - }), - }); - } - - renderDelegate(template = '{{action-delegate}}', context = {}) { - let root = this; - context = Object.assign(context, { - send(actionName, ...args) { - root.sendCount++; - root.actionCounts[actionName] = root.actionCounts[actionName] || 0; - root.actionCounts[actionName]++; - root.actionArguments = args; - }, - }); - this.render(template, context); - } - - assertSendCount(count) { - this.assert.equal(this.sendCount, count, `Send was called ${count} time(s)`); - } - - assertNamedSendCount(actionName, count) { - this.assert.equal( - this.actionCounts[actionName], - count, - `An action named '${actionName}' was sent ${count} times` - ); - } - - assertSentWithArgs(expected, message = 'arguments were sent with the action') { - this.assert.deepEqual(this.actionArguments, expected, message); - } - - ['@test Calling sendAction on a component without an action defined does nothing']() { - this.renderDelegate(); - - expectSendActionDeprecation(() => { - runTask(() => this.delegate.sendAction()); - }); - - this.assertSendCount(0); - } - - ['@test Calling sendAction on a component with an action defined calls send on the controller']() { - this.renderDelegate(); - - expectSendActionDeprecation(() => { - runTask(() => { - set(this.delegate, 'action', 'addItem'); - this.delegate.sendAction(); - }); - }); - - this.assertSendCount(1); - this.assertNamedSendCount('addItem', 1); - } - - ['@test Calling sendAction on a component with a function calls the function']() { - this.assert.expect(2); - - this.renderDelegate(); - - expectSendActionDeprecation(() => { - runTask(() => { - set(this.delegate, 'action', () => this.assert.ok(true, 'function is called')); - this.delegate.sendAction(); - }); - }); - } - - ['@test Calling sendAction on a component with a function calls the function with arguments']() { - this.assert.expect(2); - let argument = {}; - - this.renderDelegate(); - expectSendActionDeprecation(() => { - runTask(() => { - set(this.delegate, 'action', (actualArgument) => { - this.assert.deepEqual(argument, actualArgument, 'argument is passed'); - }); - this.delegate.sendAction('action', argument); - }); - }); - } - - // TODO consolidate these next 2 tests - ['@test Calling sendAction on a component with a reference attr calls the function with arguments']() { - this.renderDelegate('{{action-delegate playing=this.playing}}', { - playing: null, - }); - - expectSendActionDeprecation(() => { - runTask(() => this.delegate.sendAction()); - }); - - this.assertSendCount(0); - - runTask(() => { - set(this.context, 'playing', 'didStartPlaying'); - }); - expectSendActionDeprecation(() => { - runTask(() => { - this.delegate.sendAction('playing'); - }); - }); - - this.assertSendCount(1); - this.assertNamedSendCount('didStartPlaying', 1); - } - - ['@test Calling sendAction on a component with a {{mut}} attr calls the function with arguments']() { - this.renderDelegate('{{action-delegate playing=(mut this.playing)}}', { - playing: null, - }); - - expectSendActionDeprecation(() => { - runTask(() => this.delegate.sendAction('playing')); - }); - - this.assertSendCount(0); - - runTask(() => this.delegate.attrs.playing.update('didStartPlaying')); - expectSendActionDeprecation(() => { - runTask(() => this.delegate.sendAction('playing')); - }); - - this.assertSendCount(1); - this.assertNamedSendCount('didStartPlaying', 1); - } - - ["@test Calling sendAction with a named action uses the component's property as the action name"]() { - this.renderDelegate(); - - let component = this.delegate; - expectSendActionDeprecation(() => { - runTask(() => { - set(this.delegate, 'playing', 'didStartPlaying'); - component.sendAction('playing'); - }); - }); - - this.assertSendCount(1); - this.assertNamedSendCount('didStartPlaying', 1); - - expectSendActionDeprecation(() => { - runTask(() => component.sendAction('playing')); - }); - - this.assertSendCount(2); - this.assertNamedSendCount('didStartPlaying', 2); - - expectSendActionDeprecation(() => { - runTask(() => { - set(component, 'action', 'didDoSomeBusiness'); - component.sendAction(); - }); - }); - - this.assertSendCount(3); - this.assertNamedSendCount('didDoSomeBusiness', 1); - } - - ['@test Calling sendAction when the action name is not a string raises an exception']() { - this.renderDelegate(); - - runTask(() => { - set(this.delegate, 'action', {}); - set(this.delegate, 'playing', {}); - }); - expectSendActionDeprecation(() => { - expectAssertion(() => this.delegate.sendAction()); - }); - expectSendActionDeprecation(() => { - expectAssertion(() => this.delegate.sendAction('playing')); - }); - } - - ['@test Calling sendAction on a component with contexts']() { - this.renderDelegate(); - - let testContext = { song: 'She Broke My Ember' }; - let firstContext = { song: 'She Broke My Ember' }; - let secondContext = { song: 'My Achey Breaky Ember' }; - - expectSendActionDeprecation(() => { - runTask(() => { - set(this.delegate, 'playing', 'didStartPlaying'); - this.delegate.sendAction('playing', testContext); - }); - }); - - this.assertSendCount(1); - this.assertNamedSendCount('didStartPlaying', 1); - this.assertSentWithArgs([testContext], 'context was sent with the action'); - - expectSendActionDeprecation(() => { - runTask(() => { - this.delegate.sendAction('playing', firstContext, secondContext); - }); - }); - - this.assertSendCount(2); - this.assertNamedSendCount('didStartPlaying', 2); - this.assertSentWithArgs( - [firstContext, secondContext], - 'multiple contexts were sent to the action' - ); - } - - ['@test calling sendAction on a component within a block sends to the outer scope GH#14216']( - assert - ) { - let testContext = this; - // overrides default action-delegate so actions can be added - this.registerComponent('action-delegate', { - ComponentClass: Component.extend({ - init() { - this._super(); - testContext.delegate = this; - this.name = 'action-delegate'; - }, - - actions: { - derp(arg1) { - assert.ok(true, 'action called on action-delgate'); - assert.equal(arg1, 'something special', 'argument passed through properly'); - }, - }, - }), - - template: strip` - {{#component-a}} - {{component-b bar="derp"}} - {{/component-a}} - `, - }); - - this.registerComponent('component-a', { - ComponentClass: Component.extend({ - init() { - this._super(...arguments); - this.name = 'component-a'; - }, - actions: { - derp() { - assert.ok(false, 'no! bad scoping!'); - }, - }, - }), - }); - - let innerChild; - this.registerComponent('component-b', { - ComponentClass: Component.extend({ - init() { - this._super(...arguments); - innerChild = this; - this.name = 'component-b'; - }, - }), - }); - - this.renderDelegate(); - expectSendActionDeprecation(() => { - 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 this.shouldRender}}{{rip-alley}}{{/if}}', { - shouldRender: true, - }); - - 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 ApplicationTestCase { - ["@test sendAction should trigger an action on the parent component's controller if it exists"]( - assert - ) { - assert.expect(20); - - let component; - - this.router.map(function () { - this.route('a'); - this.route('b'); - this.route('c', function () { - this.route('d'); - this.route('e'); - }); - }); - - this.addComponent('foo-bar', { - ComponentClass: Component.extend({ - init() { - this._super(...arguments); - component = this; - }, - }), - template: `{{this.val}}`, - }); - - this.add( - 'controller:a', - Controller.extend({ - send(actionName, actionContext) { - assert.equal( - actionName, - 'poke', - 'send() method was invoked from a top level controller' - ); - assert.equal( - actionContext, - 'top', - 'action arguments were passed into the top level controller' - ); - }, - }) - ); - this.addTemplate('a', '{{foo-bar val="a" poke="poke"}}'); - - this.add( - 'route:b', - Route.extend({ - actions: { - poke(actionContext) { - assert.ok(true, 'Unhandled action sent to route'); - assert.equal(actionContext, 'top no controller'); - }, - }, - }) - ); - this.addTemplate('b', '{{foo-bar val="b" poke="poke"}}'); - - this.add( - 'route:c', - Route.extend({ - actions: { - poke(actionContext) { - assert.ok(true, 'Unhandled action sent to route'); - assert.equal(actionContext, 'top with nested no controller'); - }, - }, - }) - ); - this.addTemplate('c', '{{foo-bar val="c" poke="poke"}}{{outlet}}'); - - this.add('route:c.d', Route.extend({})); - - this.add( - 'controller:c.d', - Controller.extend({ - send(actionName, actionContext) { - assert.equal(actionName, 'poke', 'send() method was invoked from a nested controller'); - assert.equal( - actionContext, - 'nested', - 'action arguments were passed into the nested controller' - ); - }, - }) - ); - this.addTemplate('c.d', '{{foo-bar val=".d" poke="poke"}}'); - - this.add( - 'route:c.e', - Route.extend({ - actions: { - poke(actionContext) { - assert.ok(true, 'Unhandled action sent to route'); - assert.equal(actionContext, 'nested no controller'); - }, - }, - }) - ); - this.addTemplate('c.e', '{{foo-bar val=".e" poke="poke"}}'); - - return this.visit('/a') - .then(() => { - expectSendActionDeprecation(() => component.sendAction('poke', 'top')); - }) - .then(() => { - this.assertText('a'); - return this.visit('/b'); - }) - .then(() => { - expectSendActionDeprecation(() => component.sendAction('poke', 'top no controller')); - }) - .then(() => { - this.assertText('b'); - return this.visit('/c'); - }) - .then(() => { - expectSendActionDeprecation(() => { - component.sendAction('poke', 'top with nested no controller'); - }); - }) - .then(() => { - this.assertText('c'); - return this.visit('/c/d'); - }) - .then(() => { - expectSendActionDeprecation(() => component.sendAction('poke', 'nested')); - }) - .then(() => { - this.assertText('c.d'); - return this.visit('/c/e'); - }) - .then(() => { - expectSendActionDeprecation(() => component.sendAction('poke', 'nested no controller')); - }) - .then(() => this.assertText('c.e')); - } - - ["@test sendAction should not trigger an action in an outlet's controller if a parent component handles it"]( - assert - ) { - assert.expect(2); - - let component; - - this.addComponent('x-parent', { - ComponentClass: Component.extend({ - actions: { - poke() { - assert.ok(true, 'parent component handled the aciton'); - }, - }, - }), - template: '{{x-child poke="poke"}}', - }); - - this.addComponent('x-child', { - ComponentClass: Component.extend({ - init() { - this._super(...arguments); - component = this; - }, - }), - }); - - this.addTemplate('application', '{{x-parent}}'); - this.add( - 'controller:application', - Controller.extend({ - send() { - throw new Error('controller action should not be called'); - }, - }) - ); - - return this.visit('/').then(() => { - expectSendActionDeprecation(() => component.sendAction('poke')); - }); - } - } -); - -moduleFor( - 'Components test: sendAction of a closure action', - class extends RenderingTestCase { - ['@test action should be called'](assert) { - assert.expect(2); - let component; - - this.registerComponent('inner-component', { - ComponentClass: Component.extend({ - init() { - this._super(...arguments); - component = this; - }, - }), - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: Component.extend({ - outerSubmit() { - assert.ok(true, 'outerSubmit called'); - }, - }), - template: '{{inner-component submitAction=(action this.outerSubmit)}}', - }); - - this.render('{{outer-component}}'); - expectSendActionDeprecation(() => { - runTask(() => component.sendAction('submitAction')); - }); - } - - ['@test contexts passed to sendAction are appended to the bound arguments on a closure action']() { - let first = 'mitch'; - let second = 'martin'; - let third = 'matt'; - let fourth = 'wacky wycats'; - - let innerComponent; - let actualArgs; - - this.registerComponent('inner-component', { - ComponentClass: Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - }), - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: Component.extend({ - third, - actions: { - outerSubmit() { - actualArgs = [...arguments]; - }, - }, - }), - template: `{{inner-component innerSubmit=(action (action "outerSubmit" "${first}") "${second}" this.third)}}`, - }); - - this.render('{{outer-component}}'); - expectSendActionDeprecation(() => { - runTask(() => innerComponent.sendAction('innerSubmit', fourth)); - }); - - this.assert.deepEqual( - actualArgs, - [first, second, third, fourth], - 'action has the correct args' - ); - } - } -); - moduleFor( 'Components test: send', class extends RenderingTestCase { diff --git a/packages/@ember/-internals/glimmer/tests/integration/helpers/element-action-test.js b/packages/@ember/-internals/glimmer/tests/integration/helpers/element-action-test.js index 70dc35952ab..8198e78d689 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/helpers/element-action-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/helpers/element-action-test.js @@ -1500,9 +1500,7 @@ moduleFor( let InnerComponent = Component.extend({ click() { innerClickCalled = true; - expectDeprecation(() => { - this.sendAction(); - }, /You called (.*).sendAction\((.*)\) but Component#sendAction is deprecated. Please use closure actions instead./); + this.action(); }, }); @@ -1510,7 +1508,7 @@ moduleFor( ComponentClass: OuterComponent, template: strip` {{#middle-component}} - {{inner-component action="hey"}} + {{inner-component action=(action "hey")}} {{/middle-component}} `, }); diff --git a/packages/@ember/-internals/views/lib/mixins/action_support.js b/packages/@ember/-internals/views/lib/mixins/action_support.js index db831a8aa7e..c8119c1e52b 100644 --- a/packages/@ember/-internals/views/lib/mixins/action_support.js +++ b/packages/@ember/-internals/views/lib/mixins/action_support.js @@ -3,9 +3,7 @@ */ import { inspect } from '@ember/-internals/utils'; import { Mixin, get } from '@ember/-internals/metal'; -import { assert, deprecate } from '@ember/debug'; -import { MUTABLE_CELL } from '../compat/attrs'; -import { SEND_ACTION } from '@ember/deprecated-features'; +import { assert } from '@ember/debug'; const mixinObj = { send(actionName, ...args) { @@ -36,155 +34,6 @@ const mixinObj = { }, }; -if (SEND_ACTION) { - /** - Calls an action passed to a component. - - For example a component for playing or pausing music may translate click events - into action notifications of "play" or "stop" depending on some internal state - of the component: - - ```app/components/play-button.js - import Component from '@ember/component'; - - export default Component.extend({ - click() { - if (this.get('isPlaying')) { - this.sendAction('play'); - } else { - this.sendAction('stop'); - } - } - }); - ``` - - The actions "play" and "stop" must be passed to this `play-button` component: - - ```handlebars - {{! app/templates/application.hbs }} - {{play-button play=(action "musicStarted") stop=(action "musicStopped")}} - ``` - - When the component receives a browser `click` event it translate this - interaction into application-specific semantics ("play" or "stop") and - calls the specified action. - - ```app/controller/application.js - import Controller from '@ember/controller'; - - export default Controller.extend({ - actions: { - musicStarted() { - // called when the play button is clicked - // and the music started playing - }, - musicStopped() { - // called when the play button is clicked - // and the music stopped playing - } - } - }); - ``` - - If no action is passed to `sendAction` a default name of "action" - is assumed. - - ```app/components/next-button.js - import Component from '@ember/component'; - - export default Component.extend({ - click() { - this.sendAction(); - } - }); - ``` - - ```handlebars - {{! app/templates/application.hbs }} - {{next-button action=(action "playNextSongInAlbum")}} - ``` - - ```app/controllers/application.js - import Controller from '@ember/controller'; - - export default Controller.extend({ - actions: { - playNextSongInAlbum() { - ... - } - } - }); - ``` - - @method sendAction - @param [action] {String} the action to call - @param [params] {*} arguments for the action - @public - @deprecated - */ - let sendAction = function sendAction(action, ...contexts) { - assert( - `Attempted to call .sendAction() with the action '${action}' on the destroyed object '${this}'.`, - !this.isDestroying && !this.isDestroyed - ); - deprecate( - `You called ${inspect(this)}.sendAction(${ - typeof action === 'string' ? `"${action}"` : '' - }) but Component#sendAction is deprecated. Please use closure actions instead.`, - false, - { - id: 'ember-component.send-action', - until: '4.0.0', - url: 'https://deprecations.emberjs.com/v3.x#toc_ember-component-send-action', - for: 'ember-source', - since: { - enabled: '3.4.0', - }, - } - ); - - let actionName; - - // Send the default action - if (action === undefined) { - action = 'action'; - } - actionName = get(this, `attrs.${action}`) || get(this, action); - actionName = validateAction(this, actionName); - - // If no action name for that action could be found, just abort. - if (actionName === undefined) { - return; - } - - if (typeof actionName === 'function') { - actionName(...contexts); - } else { - this.triggerAction({ - action: actionName, - actionContext: contexts, - }); - } - }; - - let validateAction = function validateAction(component, actionName) { - if (actionName && actionName[MUTABLE_CELL]) { - actionName = actionName.value; - } - - assert( - `The default action was triggered on the component ${component.toString()}, but the action name (${actionName}) was not a string.`, - actionName === null || - actionName === undefined || - typeof actionName === 'string' || - typeof actionName === 'function' - ); - return actionName; - }; - - mixinObj.sendAction = sendAction; -} - /** @class ActionSupport @namespace Ember diff --git a/packages/@ember/-internals/views/lib/mixins/text_support.js b/packages/@ember/-internals/views/lib/mixins/text_support.js index ef78f4ec7a5..e72590c6cb6 100644 --- a/packages/@ember/-internals/views/lib/mixins/text_support.js +++ b/packages/@ember/-internals/views/lib/mixins/text_support.js @@ -5,7 +5,6 @@ import { get, set, Mixin } from '@ember/-internals/metal'; import { EMBER_MODERNIZED_BUILT_IN_COMPONENTS } from '@ember/canary-features'; import { deprecate } from '@ember/debug'; -import { SEND_ACTION } from '@ember/deprecated-features'; import { MUTABLE_CELL } from '@ember/-internals/views'; import { DEBUG } from '@glimmer/env'; @@ -327,24 +326,7 @@ function sendAction(eventName, view, event) { let value = get(view, 'value'); - if (SEND_ACTION && typeof action === 'string') { - let message = `Passing actions to components as strings (like \`\`) is deprecated. Please use closure actions instead (\`\`).`; - - deprecate(message, false, { - id: 'ember-component.send-action', - until: '4.0.0', - url: 'https://deprecations.emberjs.com/v3.x#toc_ember-component-send-action', - for: 'ember-source', - since: { - enabled: '3.4.0', - }, - }); - - view.triggerAction({ - action: action, - actionContext: [value, event], - }); - } else if (typeof action === 'function') { + if (typeof action === 'function') { action(value, event); } diff --git a/packages/@ember/deprecated-features/index.ts b/packages/@ember/deprecated-features/index.ts index 354f3956f66..2da36f33344 100644 --- a/packages/@ember/deprecated-features/index.ts +++ b/packages/@ember/deprecated-features/index.ts @@ -3,7 +3,6 @@ // These versions should be the version that the deprecation was _introduced_, // not the version that the feature will be removed. -export const SEND_ACTION = !!'3.4.0'; export const ROUTER_EVENTS = !!'4.0.0'; export const COMPONENT_MANAGER_STRING_LOOKUP = !!'3.8.0'; export const JQUERY_INTEGRATION = !!'3.9.0'; diff --git a/packages/ember-template-compiler/lib/plugins/deprecate-send-action.ts b/packages/ember-template-compiler/lib/plugins/deprecate-send-action.ts deleted file mode 100644 index 5d72fb778bd..00000000000 --- a/packages/ember-template-compiler/lib/plugins/deprecate-send-action.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { deprecate } from '@ember/debug'; -import { SEND_ACTION } from '@ember/deprecated-features'; -import { AST, ASTPlugin } from '@glimmer/syntax'; -import calculateLocationDisplay from '../system/calculate-location-display'; -import { EmberASTPluginEnvironment } from '../types'; -import { isPath } from './utils'; - -const EVENTS = [ - 'insert-newline', - 'enter', - 'escape-press', - 'focus-in', - 'focus-out', - 'key-press', - 'key-up', - 'key-down', -]; - -export default function deprecateSendAction(env: EmberASTPluginEnvironment): ASTPlugin { - if (SEND_ACTION) { - let moduleName = env.meta?.moduleName; - - let deprecationMessage = (node: AST.Node, eventName: string, actionName: string) => { - let sourceInformation = calculateLocationDisplay(moduleName, node.loc); - - if (node.type === 'ElementNode') { - return `Passing actions to components as strings (like \`\`) is deprecated. Please use closure actions instead (\`\`). ${sourceInformation}`; - } else { - return `Passing actions to components as strings (like \`{{input ${eventName}="${actionName}"}}\`) is deprecated. Please use closure actions instead (\`{{input ${eventName}=(action "${actionName}")}}\`). ${sourceInformation}`; - } - }; - - return { - name: 'deprecate-send-action', - - visitor: { - ElementNode(node: AST.ElementNode) { - if (node.tag !== 'Input') { - return; - } - - node.attributes.forEach(({ name, value }) => { - if (name.charAt(0) === '@') { - let eventName = name.substring(1); - - if (EVENTS.indexOf(eventName) > -1) { - if (value.type === 'TextNode') { - deprecate(deprecationMessage(node, eventName, value.chars), false, { - id: 'ember-component.send-action', - until: '4.0.0', - url: 'https://deprecations.emberjs.com/v3.x#toc_ember-component-send-action', - for: 'ember-source', - since: { - enabled: '3.4.0', - }, - }); - } else if ( - value.type === 'MustacheStatement' && - value.path.type === 'StringLiteral' - ) { - deprecate(deprecationMessage(node, eventName, value.path.original), false, { - id: 'ember-component.send-action', - until: '4.0.0', - url: 'https://deprecations.emberjs.com/v3.x#toc_ember-component-send-action', - for: 'ember-source', - since: { - enabled: '3.4.0', - }, - }); - } - } - } - }); - }, - - MustacheStatement(node: AST.MustacheStatement) { - if (!isPath(node.path) || node.path.original !== 'input') { - return; - } - - node.hash.pairs.forEach((pair) => { - if (EVENTS.indexOf(pair.key) > -1 && pair.value.type === 'StringLiteral') { - deprecate(deprecationMessage(node, pair.key, pair.value.original), false, { - id: 'ember-component.send-action', - until: '4.0.0', - url: 'https://deprecations.emberjs.com/v3.x#toc_ember-component-send-action', - for: 'ember-source', - since: { - enabled: '3.4.0', - }, - }); - } - }); - }, - }, - }; - } - - return { - name: 'deprecate-send-action', - visitor: {}, - }; -} diff --git a/packages/ember-template-compiler/lib/plugins/index.ts b/packages/ember-template-compiler/lib/plugins/index.ts index 525bea17e25..7d610b69e96 100644 --- a/packages/ember-template-compiler/lib/plugins/index.ts +++ b/packages/ember-template-compiler/lib/plugins/index.ts @@ -3,7 +3,6 @@ import AssertAgainstNamedBlocks from './assert-against-named-blocks'; import AssertInputHelperWithoutBlock from './assert-input-helper-without-block'; import AssertReservedNamedArguments from './assert-reserved-named-arguments'; import AssertSplattributeExpressions from './assert-splattribute-expression'; -import DeprecateSendAction from './deprecate-send-action'; import TransformActionSyntax from './transform-action-syntax'; import TransformAttrsIntoArgs from './transform-attrs-into-args'; import TransformEachInIntoEach from './transform-each-in-into-each'; @@ -17,7 +16,6 @@ import TransformResolutions from './transform-resolutions'; import TransformWrapMountAndOutlet from './transform-wrap-mount-and-outlet'; import { EMBER_DYNAMIC_HELPERS_AND_MODIFIERS, EMBER_NAMED_BLOCKS } from '@ember/canary-features'; -import { SEND_ACTION } from '@ember/deprecated-features'; // order of plugins is important export const RESOLUTION_MODE_TRANSFORMS = Object.freeze( @@ -35,7 +33,6 @@ export const RESOLUTION_MODE_TRANSFORMS = Object.freeze( AssertSplattributeExpressions, TransformEachTrackArray, TransformWrapMountAndOutlet, - SEND_ACTION ? DeprecateSendAction : null, !EMBER_NAMED_BLOCKS ? AssertAgainstNamedBlocks : null, EMBER_DYNAMIC_HELPERS_AND_MODIFIERS ? TransformResolutions @@ -53,7 +50,6 @@ export const STRICT_MODE_TRANSFORMS = Object.freeze( AssertSplattributeExpressions, TransformEachTrackArray, TransformWrapMountAndOutlet, - SEND_ACTION ? DeprecateSendAction : null, !EMBER_NAMED_BLOCKS ? AssertAgainstNamedBlocks : null, !EMBER_DYNAMIC_HELPERS_AND_MODIFIERS ? AssertAgainstDynamicHelpersModifiers : null, ].filter(notNull) diff --git a/packages/ember-template-compiler/tests/plugins/deprecate-send-action-test.js b/packages/ember-template-compiler/tests/plugins/deprecate-send-action-test.js deleted file mode 100644 index e5276727e14..00000000000 --- a/packages/ember-template-compiler/tests/plugins/deprecate-send-action-test.js +++ /dev/null @@ -1,29 +0,0 @@ -import { compile } from '../../index'; -import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; - -const EVENTS = [ - 'insert-newline', - 'enter', - 'escape-press', - 'focus-in', - 'focus-out', - 'key-press', - 'key-up', - 'key-down', -]; - -class DeprecateSendActionTest extends AbstractTestCase {} - -EVENTS.forEach(function (e) { - DeprecateSendActionTest.prototype[ - `@test Using \`{{input ${e}="actionName"}}\` provides a deprecation` - ] = function () { - let expectedMessage = `Passing actions to components as strings (like \`{{input ${e}="foo-bar"}}\`) is deprecated. Please use closure actions instead (\`{{input ${e}=(action "foo-bar")}}\`). ('baz/foo-bar' @ L1:C0) `; - - expectDeprecation(() => { - compile(`{{input ${e}="foo-bar"}}`, { moduleName: 'baz/foo-bar' }); - }, expectedMessage); - }; -}); - -moduleFor('ember-template-compiler: deprecate-send-action', DeprecateSendActionTest); diff --git a/tests/docs/expected.js b/tests/docs/expected.js index 354287d995e..bbb206e2807 100644 --- a/tests/docs/expected.js +++ b/tests/docs/expected.js @@ -510,7 +510,6 @@ module.exports = { 'schedule', 'scheduleOnce', 'send', - 'sendAction', 'sendEvent', 'serialize', 'serializeQueryParam',