diff --git a/packages/@ember/-internals/glimmer/lib/helpers/action.ts b/packages/@ember/-internals/glimmer/lib/helpers/action.ts deleted file mode 100644 index 8f2423972ab..00000000000 --- a/packages/@ember/-internals/glimmer/lib/helpers/action.ts +++ /dev/null @@ -1,441 +0,0 @@ -/** -@module ember -*/ -import { get } from '@ember/-internals/metal'; -import type { AnyFn } from '@ember/-internals/utility-types'; -import { assert } from '@ember/debug'; -import { flaggedInstrument } from '@ember/instrumentation'; -import { join } from '@ember/runloop'; -import { DEBUG } from '@glimmer/env'; -import type { CapturedArguments } from '@glimmer/interfaces'; -import type { Reference } from '@glimmer/reference'; -import { createUnboundRef, isInvokableRef, updateRef, valueForRef } from '@glimmer/reference'; -import { internalHelper } from './internal-helper'; - -export const ACTIONS = new WeakSet(); - -/** - The `{{action}}` helper provides a way to pass triggers for behavior (usually - just a function) between components, and into components from controllers. - - ### Passing functions with the action helper - - There are three contexts an action helper can be used in. The first two - contexts to discuss are attribute context, and Handlebars value context. - - ```handlebars - {{! An example of attribute context }} -
- {{! Examples of Handlebars value context }} - {{input on-input=(action "save")}} - {{yield (action "refreshData") andAnotherParam}} - ``` - - In these contexts, - the helper is called a "closure action" helper. Its behavior is simple: - If passed a function name, read that function off the `actions` property - of the current context. Once that function is read, or immediately if a function was - passed, create a closure over that function and any arguments. - The resulting value of an action helper used this way is simply a function. - - For example, in the attribute context: - - ```handlebars - {{! An example of attribute context }} -
- ``` - - The resulting template render logic would be: - - ```js - var div = document.createElement('div'); - var actionFunction = (function(context){ - return function() { - return context.actions.save.apply(context, arguments); - }; - })(context); - div.onclick = actionFunction; - ``` - - Thus when the div is clicked, the action on that context is called. - Because the `actionFunction` is just a function, closure actions can be - passed between components and still execute in the correct context. - - Here is an example action handler on a component: - - ```app/components/my-component.js - import Component from '@glimmer/component'; - import { action } from '@ember/object'; - - export default class extends Component { - @action - save() { - this.model.save(); - } - } - ``` - - Actions are always looked up on the `actions` property of the current context. - This avoids collisions in the naming of common actions, such as `destroy`. - Two options can be passed to the `action` helper when it is used in this way. - - * `target=someProperty` will look to `someProperty` instead of the current - context for the `actions` hash. This can be useful when targeting a - service for actions. - * `value="target.value"` will read the path `target.value` off the first - argument to the action when it is called and rewrite the first argument - to be that value. This is useful when attaching actions to event listeners. - - ### Invoking an action - - Closure actions curry both their scope and any arguments. When invoked, any - additional arguments are added to the already curried list. - 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'; - import { action } from '@ember/object'; - - export default class extends Component { - @action - setName(model, name) { - model.set('name', name); - } - } - ``` - - ```app/components/update-name.hbs - {{input on-input=(action (action 'setName' @model) value="target.value")}} - ``` - - The first argument (`@model`) was curried over, and the run-time argument (`event`) - becomes a second argument. Action calls can be nested this way because each simply - returns a function. Any function can be passed to the `{{action}}` helper, including - other actions. - - Actions invoked with `sendAction` have the same currying behavior as demonstrated - with `on-input` above. For example: - - ```app/components/my-input.js - import Component from '@glimmer/component'; - import { action } from '@ember/object'; - - export default class extends Component { - @action - setName(model, name) { - model.set('name', name); - } - } - ``` - - ```handlebars - - ``` - - or - - ```handlebars - {{my-input submit=(action 'setName' @model)}} - ``` - - ```app/components/my-component.js - import Component from '@ember/component'; - - export default Component.extend({ - click() { - // Note that model is not passed, it was curried in the template - this.submit('bob'); - } - }); - ``` - - ### Attaching actions to DOM elements - - The third context of the `{{action}}` helper can be called "element space". - For example: - - ```handlebars - {{! An example of element space }} -
- ``` - - Used this way, the `{{action}}` helper provides a useful shortcut for - registering an HTML element in a template for a single DOM event and - forwarding that interaction to the template's context (controller or component). - If the context of a template is a controller, actions used this way will - bubble to routes when the controller does not implement the specified action. - Once an action hits a route, it will bubble through the route hierarchy. - - ### Event Propagation - - `{{action}}` helpers called in element space can control event bubbling. Note - that the closure style actions cannot. - - Events triggered through the action helper will automatically have - `.preventDefault()` called on them. You do not need to do so in your event - handlers. If you need to allow event propagation (to handle file inputs for - example) you can supply the `preventDefault=false` option to the `{{action}}` helper: - - ```handlebars -
- - -
- ``` - - To disable bubbling, pass `bubbles=false` to the helper: - - ```handlebars - - ``` - - To disable bubbling with closure style actions you must create your own - wrapper helper that makes use of `event.stopPropagation()`: - - ```handlebars -
Hello
- ``` - - ```app/helpers/disable-bubbling.js - import { helper } from '@ember/component/helper'; - - export function disableBubbling([action]) { - return function(event) { - event.stopPropagation(); - return action(event); - }; - } - export default helper(disableBubbling); - ``` - - If you need the default handler to trigger you should either register your - own event handler, or use event methods on your view class. See - ["Responding to Browser Events"](/ember/release/classes/Component) - in the documentation for `Component` for more information. - - ### Specifying DOM event type - - `{{action}}` helpers called in element space can specify an event type. - By default the `{{action}}` helper registers for DOM `click` events. You can - supply an `on` option to the helper to specify a different DOM event name: - - ```handlebars -
- click me -
- ``` - - See ["Event Names"](/ember/release/classes/Component) for a list of - acceptable DOM event names. - - ### Specifying whitelisted modifier keys - - `{{action}}` helpers called in element space can specify modifier keys. - By default the `{{action}}` helper will ignore click events with pressed modifier - keys. You can supply an `allowedKeys` option to specify which keys should not be ignored. - - ```handlebars -
- click me -
- ``` - - This way the action will fire when clicking with the alt key pressed down. - Alternatively, supply "any" to the `allowedKeys` option to accept any combination of modifier keys. - - ```handlebars -
- click me with any key pressed -
- ``` - - ### Specifying a Target - - A `target` option can be provided to the helper to change - which object will receive the method call. This option must be a path - to an object, accessible in the current context: - - ```app/templates/application.hbs -
- click me -
- ``` - - ```app/controllers/application.js - import Controller from '@ember/controller'; - import { service } from '@ember/service'; - - export default class extends Controller { - @service someService; - } - ``` - - @method action - @for Ember.Templates.helpers - @public -*/ -export default internalHelper((args: CapturedArguments): Reference => { - let { named, positional } = args; - // The first two argument slots are reserved. - // pos[0] is the context (or `this`) - // pos[1] is the action name or function - // Anything else is an action argument. - let [context, action, ...restArgs] = positional; - assert('hash position arguments', context && action); - - let debugKey: string = action.debugLabel!; - - let target = 'target' in named ? named['target'] : context; - let processArgs = makeArgsProcessor(('value' in named && named['value']) || false, restArgs); - - let fn: Function; - - if (isInvokableRef(action)) { - fn = makeClosureAction(action, action as MaybeActionHandler, invokeRef, processArgs, debugKey); - } else { - fn = makeDynamicClosureAction( - valueForRef(context) as object, - // SAFETY: glimmer-vm should expose narrowing utilities for references - // as is, `target` is still `Reference`. - // however, we never even tried to narrow `target`, so this is potentially risky code. - target!, - // SAFETY: glimmer-vm should expose narrowing utilities for references - // as is, `action` is still `Reference` - action, - processArgs, - debugKey - ); - } - - ACTIONS.add(fn); - - return createUnboundRef(fn, '(result of an `action` helper)'); -}); - -function NOOP(args: unknown[]) { - return args; -} - -function makeArgsProcessor(valuePathRef: Reference | false, actionArgsRef: Reference[]) { - let mergeArgs: ((args: unknown[]) => unknown[]) | undefined; - - if (actionArgsRef.length > 0) { - mergeArgs = (args: unknown[]) => { - return actionArgsRef.map(valueForRef).concat(args); - }; - } - - let readValue: ((args: unknown[]) => unknown[]) | undefined; - - if (valuePathRef) { - readValue = (args: unknown[]) => { - let valuePath = valueForRef(valuePathRef); - - if (valuePath && args.length > 0) { - args[0] = get(args[0] as object, valuePath as string); - } - - return args; - }; - } - - if (mergeArgs && readValue) { - return (args: unknown[]) => { - return readValue!(mergeArgs!(args)); - }; - } else { - return mergeArgs || readValue || NOOP; - } -} - -function makeDynamicClosureAction( - context: object, - targetRef: Reference, - actionRef: Reference, - processArgs: (args: unknown[]) => unknown[], - debugKey: string -) { - const action = valueForRef(actionRef); - - // We don't allow undefined/null values, so this creates a throw-away action to trigger the assertions - if (DEBUG) { - makeClosureAction(context, valueForRef(targetRef), action, processArgs, debugKey); - } - - return (...args: any[]) => { - return makeClosureAction( - context, - valueForRef(targetRef), - action, - processArgs, - debugKey - )(...args); - }; -} - -interface MaybeActionHandler { - actions?: Record; -} - -function makeClosureAction( - context: object, - target: unknown, - action: unknown | null | undefined | string | Function, - processArgs: (args: unknown[]) => unknown[], - debugKey: string -) { - let self: object; - let fn: Function; - - assert( - `Action passed is null or undefined in (action) from ${target}.`, - action !== undefined && action !== null - ); - - if (typeof action === 'string') { - assert('target must be an object', target !== null && typeof target === 'object'); - self = target; - let value = (target as { actions?: Record }).actions?.[action]; - assert(`An action named '${action}' was not found in ${target}`, Boolean(value)); - assert( - `An action named '${action}' was found in ${target}, but is not a function`, - typeof value === 'function' - ); - fn = value; - } else if (typeof action === 'function') { - self = context; - fn = action; - } else { - assert( - `An action could not be made for \`${ - debugKey || action - }\` in ${target}. Please confirm that you are using either a quoted action name (i.e. \`(action '${ - debugKey || 'myAction' - }')\`) or a function available in ${target}.`, - false - ); - } - - return (...args: any[]) => { - let payload = { target: self, args, label: '@glimmer/closure-action' }; - return flaggedInstrument('interaction.ember-action', payload, () => { - return join(self, fn as AnyFn, ...processArgs(args)); - }); - }; -} - -// The code above: - -// 1. Finds an action function, usually on the `actions` hash -// 2. Calls it with the target as the correct `this` context - -// Previously, `UPDATE_REFERENCED_VALUE` was a method on the reference itself, -// so this made a bit more sense. Now, it isn't, and so we need to create a -// function that can have `this` bound to it when called. This allows us to use -// the same codepath to call `updateRef` on the reference. -function invokeRef(this: Reference, value: unknown) { - updateRef(this, value); -} diff --git a/packages/@ember/-internals/glimmer/lib/modifiers/action.ts b/packages/@ember/-internals/glimmer/lib/modifiers/action.ts deleted file mode 100644 index 249eaeb9436..00000000000 --- a/packages/@ember/-internals/glimmer/lib/modifiers/action.ts +++ /dev/null @@ -1,285 +0,0 @@ -import type { InternalOwner } from '@ember/-internals/owner'; -import { uuid } from '@ember/-internals/utils'; -import { ActionManager, EventDispatcher, isSimpleClick } from '@ember/-internals/views'; -import { assert } from '@ember/debug'; -import { flaggedInstrument } from '@ember/instrumentation'; -import { join } from '@ember/runloop'; -import { registerDestructor } from '@glimmer/destroyable'; -import { DEBUG } from '@glimmer/env'; -import type { - CapturedArguments, - CapturedNamedArguments, - CapturedPositionalArguments, - InternalModifierManager, -} from '@glimmer/interfaces'; -import { setInternalModifierManager } from '@glimmer/manager'; -import { isInvokableRef, updateRef, valueForRef } from '@glimmer/reference'; -import type { UpdatableTag } from '@glimmer/validator'; -import { createUpdatableTag } from '@glimmer/validator'; -import type { SimpleElement } from '@simple-dom/interface'; - -const MODIFIERS = ['alt', 'shift', 'meta', 'ctrl']; -const POINTER_EVENT_TYPE_REGEX = /^click|mouse|touch/; - -function isAllowedEvent(event: Event, allowedKeys: any) { - if (allowedKeys === null || allowedKeys === undefined) { - if (POINTER_EVENT_TYPE_REGEX.test(event.type)) { - return isSimpleClick(event); - } else { - allowedKeys = ''; - } - } - - if (allowedKeys.indexOf('any') >= 0) { - return true; - } - - for (let i = 0; i < MODIFIERS.length; i++) { - if ((event as any)[MODIFIERS[i] + 'Key'] && allowedKeys.indexOf(MODIFIERS[i]) === -1) { - return false; - } - } - - return true; -} - -export let ActionHelper = { - // registeredActions is re-exported for compatibility with older plugins - // that were using this undocumented API. - registeredActions: ActionManager.registeredActions, - - registerAction(actionState: ActionState) { - let { actionId } = actionState; - - ActionManager.registeredActions[actionId] = actionState; - - return actionId; - }, - - unregisterAction(actionState: ActionState) { - let { actionId } = actionState; - - delete ActionManager.registeredActions[actionId]; - }, -}; - -export class ActionState { - public element: SimpleElement; - public owner: InternalOwner; - public actionId: number; - public actionName: any; - public actionArgs: any; - public namedArgs: CapturedNamedArguments; - public positional: CapturedPositionalArguments; - public implicitTarget: any; - public eventName: any; - public tag = createUpdatableTag(); - - constructor( - element: SimpleElement, - owner: InternalOwner, - actionId: number, - actionArgs: any[], - namedArgs: CapturedNamedArguments, - positionalArgs: CapturedPositionalArguments - ) { - this.element = element; - this.owner = owner; - this.actionId = actionId; - this.actionArgs = actionArgs; - this.namedArgs = namedArgs; - this.positional = positionalArgs; - this.eventName = this.getEventName(); - - registerDestructor(this, () => ActionHelper.unregisterAction(this)); - } - - getEventName() { - let { on } = this.namedArgs; - - return on !== undefined ? valueForRef(on) : 'click'; - } - - getActionArgs() { - let result = new Array(this.actionArgs.length); - - for (let i = 0; i < this.actionArgs.length; i++) { - result[i] = valueForRef(this.actionArgs[i]); - } - - return result; - } - - getTarget(): any { - let { implicitTarget, namedArgs } = this; - let { target } = namedArgs; - - return target !== undefined ? valueForRef(target) : valueForRef(implicitTarget); - } - - handler(event: Event): boolean { - let { actionName, namedArgs } = this; - let { bubbles, preventDefault, allowedKeys } = namedArgs; - - let bubblesVal = bubbles !== undefined ? valueForRef(bubbles) : undefined; - let preventDefaultVal = preventDefault !== undefined ? valueForRef(preventDefault) : undefined; - let allowedKeysVal = allowedKeys !== undefined ? valueForRef(allowedKeys) : undefined; - - let target = this.getTarget(); - - let shouldBubble = bubblesVal !== false; - - if (!isAllowedEvent(event, allowedKeysVal)) { - return true; - } - - if (preventDefaultVal !== false) { - event.preventDefault(); - } - - if (!shouldBubble) { - event.stopPropagation(); - } - - join(() => { - let args = this.getActionArgs(); - let payload = { - args, - target, - name: null, - }; - if (isInvokableRef(actionName)) { - flaggedInstrument('interaction.ember-action', payload, () => { - updateRef(actionName, args[0]); - }); - return; - } - if (typeof actionName === 'function') { - flaggedInstrument('interaction.ember-action', payload, () => { - actionName.apply(target, args); - }); - return; - } - payload.name = actionName; - if (target.send) { - flaggedInstrument('interaction.ember-action', payload, () => { - target.send.apply(target, [actionName, ...args]); - }); - } else { - assert( - `The action '${actionName}' did not exist on ${target}`, - typeof target[actionName] === 'function' - ); - flaggedInstrument('interaction.ember-action', payload, () => { - target[actionName].apply(target, args); - }); - } - }); - - return shouldBubble; - } -} - -class ActionModifierManager implements InternalModifierManager { - create( - owner: InternalOwner, - element: SimpleElement, - _state: object, - { named, positional }: CapturedArguments - ): ActionState { - let actionArgs: any[] = []; - // The first two arguments are (1) `this` and (2) the action name. - // Everything else is a param. - for (let i = 2; i < positional.length; i++) { - actionArgs.push(positional[i]); - } - - let actionId = uuid(); - - return new ActionState(element, owner, actionId, actionArgs, named, positional); - } - - getDebugName(): string { - return 'action'; - } - - install(actionState: ActionState): void { - let { element, actionId, positional } = actionState; - - let actionName; - let actionNameRef: any; - let implicitTarget; - - if (positional.length > 1) { - implicitTarget = positional[0]; - actionNameRef = positional[1]; - - if (isInvokableRef(actionNameRef)) { - actionName = actionNameRef; - } else { - actionName = valueForRef(actionNameRef); - - if (DEBUG) { - let actionPath = actionNameRef.debugLabel; - let actionPathParts = actionPath.split('.'); - let actionLabel = actionPathParts[actionPathParts.length - 1]; - - assert( - 'You specified a quoteless path, `' + - actionPath + - '`, to the ' + - '{{action}} helper which did not resolve to an action name (a ' + - 'string). Perhaps you meant to use a quoted actionName? (e.g. ' + - '{{action "' + - actionLabel + - '"}}).', - typeof actionName === 'string' || typeof actionName === 'function' - ); - } - } - } - - actionState.actionName = actionName; - actionState.implicitTarget = implicitTarget; - - this.ensureEventSetup(actionState); - ActionHelper.registerAction(actionState); - - element.setAttribute('data-ember-action', ''); - element.setAttribute(`data-ember-action-${actionId}`, String(actionId)); - } - - update(actionState: ActionState): void { - let { positional } = actionState; - let actionNameRef = positional[1]; - assert('Expected at least one positional arg', actionNameRef); - - if (!isInvokableRef(actionNameRef)) { - actionState.actionName = valueForRef(actionNameRef); - } - - let newEventName = actionState.getEventName(); - if (newEventName !== actionState.eventName) { - this.ensureEventSetup(actionState); - actionState.eventName = actionState.getEventName(); - } - } - - ensureEventSetup(actionState: ActionState): void { - let dispatcher = actionState.owner.lookup('event_dispatcher:main'); - assert('Expected dispatcher to be an EventDispatcher', dispatcher instanceof EventDispatcher); - dispatcher?.setupHandlerForEmberEvent(actionState.eventName); - } - - getTag(actionState: ActionState): UpdatableTag { - return actionState.tag; - } - - getDestroyable(actionState: ActionState): object { - return actionState; - } -} - -const ACTION_MODIFIER_MANAGER = new ActionModifierManager(); - -export default setInternalModifierManager(ACTION_MODIFIER_MANAGER, {}); diff --git a/packages/@ember/-internals/glimmer/lib/resolver.ts b/packages/@ember/-internals/glimmer/lib/resolver.ts index 5ad07efc0c4..3c4953a5246 100644 --- a/packages/@ember/-internals/glimmer/lib/resolver.ts +++ b/packages/@ember/-internals/glimmer/lib/resolver.ts @@ -37,14 +37,12 @@ import { default as inElementNullCheckHelper } from './helpers/-in-element-null- import { default as normalizeClassHelper } from './helpers/-normalize-class'; import { default as resolve } from './helpers/-resolve'; import { default as trackArray } from './helpers/-track-array'; -import { default as action } from './helpers/action'; import { default as eachIn } from './helpers/each-in'; import { default as mut } from './helpers/mut'; import { default as readonly } from './helpers/readonly'; import { default as unbound } from './helpers/unbound'; import { default as uniqueId } from './helpers/unique-id'; -import actionModifier from './modifiers/action'; import { mountHelper } from './syntax/mount'; import { outletHelper } from './syntax/outlet'; @@ -109,7 +107,6 @@ function lookupComponentPair( } const BUILTIN_KEYWORD_HELPERS: Record = { - action, mut, readonly, unbound, @@ -147,12 +144,7 @@ if (DEBUG) { BUILTIN_HELPERS['-disallow-dynamic-resolution'] = disallowDynamicResolution; } -const BUILTIN_KEYWORD_MODIFIERS: Record = { - action: actionModifier, -}; - const BUILTIN_MODIFIERS: Record = { - ...BUILTIN_KEYWORD_MODIFIERS, on, }; diff --git a/packages/@ember/-internals/glimmer/lib/utils/process-args.ts b/packages/@ember/-internals/glimmer/lib/utils/process-args.ts index 51e79131dc5..27014c8ab11 100644 --- a/packages/@ember/-internals/glimmer/lib/utils/process-args.ts +++ b/packages/@ember/-internals/glimmer/lib/utils/process-args.ts @@ -4,7 +4,6 @@ import type { Reference } from '@glimmer/reference'; import { isUpdatableRef, updateRef, valueForRef } from '@glimmer/reference'; import { assert } from '@ember/debug'; import { ARGS } from '../component-managers/curly'; -import { ACTIONS } from '../helpers/action'; // ComponentArgs takes EvaluatedNamedArgs and converts them into the // inputs needed by CurlyComponents (attrs and props, with mutable @@ -20,9 +19,7 @@ export function processComponentArgs(namedArgs: CapturedNamedArguments) { assert('expected ref', ref); let value = valueForRef(ref); - let isAction = typeof value === 'function' && ACTIONS.has(value); - - if (isUpdatableRef(ref) && !isAction) { + if (isUpdatableRef(ref)) { attrs[name] = new MutableCell(ref, value); } else { attrs[name] = value; diff --git a/packages/@ember/-internals/glimmer/tests/integration/application/actions-test.js b/packages/@ember/-internals/glimmer/tests/integration/application/actions-test.js deleted file mode 100644 index a0d3df9d997..00000000000 --- a/packages/@ember/-internals/glimmer/tests/integration/application/actions-test.js +++ /dev/null @@ -1,109 +0,0 @@ -import { moduleFor, ApplicationTestCase, RenderingTestCase, runTask } from 'internal-test-helpers'; - -import Controller from '@ember/controller'; -import { getDebugFunction, setDebugFunction } from '@ember/debug'; - -import { Component } from '../../utils/helpers'; - -const originalDebug = getDebugFunction('debug'); -const noop = function () {}; - -moduleFor( - 'Application test: actions', - class extends ApplicationTestCase { - constructor() { - setDebugFunction('debug', noop); - super(...arguments); - } - - teardown() { - setDebugFunction('debug', originalDebug); - } - - ['@test actions in top level template application template target application controller']( - assert - ) { - assert.expect(1); - - this.add( - 'controller:application', - Controller.extend({ - actions: { - handleIt() { - assert.ok(true, 'controller received action properly'); - }, - }, - }) - ); - - this.addTemplate( - 'application', - '' - ); - - return this.visit('/').then(() => { - runTask(() => this.$('#handle-it').click()); - }); - } - - ['@test actions in nested outlet template target their controller'](assert) { - assert.expect(1); - - this.add( - 'controller:application', - Controller.extend({ - actions: { - handleIt() { - assert.ok(false, 'application controller should not have received action!'); - }, - }, - }) - ); - - this.add( - 'controller:index', - Controller.extend({ - actions: { - handleIt() { - assert.ok(true, 'controller received action properly'); - }, - }, - }) - ); - - this.addTemplate('index', ''); - - return this.visit('/').then(() => { - runTask(() => this.$('#handle-it').click()); - }); - } - } -); - -moduleFor( - 'Rendering test: non-interactive actions', - class extends RenderingTestCase { - getBootOptions() { - return { isInteractive: false }; - } - - [`@test doesn't attatch actions`](assert) { - this.registerComponent('foo-bar', { - ComponentClass: Component.extend({ - actions: { - fire() { - assert.ok(false); - }, - }, - }), - template: ``, - }); - - this.render('{{foo-bar tagName=""}}'); - - this.assertHTML(''); - - this.$('button').click(); - } - } -); diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/contextual-components-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/contextual-components-test.js index 074e4e4a5c2..2b3dc653ee3 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/contextual-components-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/contextual-components-test.js @@ -773,10 +773,8 @@ moduleFor( this.registerComponent('my-action-component', { ComponentClass: Component.extend({ - actions: { - changeValue() { - this.incrementProperty('myProp'); - }, + changeValue() { + this.incrementProperty('myProp'); }, }), template: strip` @@ -784,7 +782,7 @@ moduleFor( {{api.my-nested-component}} {{/my-component}}
- `, + `, }); this.render('{{my-action-component myProp=this.model.myProp}}', { @@ -839,13 +837,12 @@ moduleFor( ['@test parameters in a contextual component are mutable when value is a param'](assert) { // This checks that a `(mut)` is added to parameters and attributes to // contextual components when it is a param. - this.registerComponent('change-button', { ComponentClass: Component.extend().reopenClass({ positionalParams: ['val'], }), template: strip` - `, }); @@ -892,15 +889,13 @@ moduleFor( this.registerComponent('outer-component', { ComponentClass: Component.extend({ message: 'hello', - actions: { - change() { - this.set('message', 'goodbye'); - }, + change() { + this.set('message', 'goodbye'); }, }), template: strip` message: {{this.message}}{{inner-component message=this.message}} - `, }); diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/curly-components-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/curly-components-test.js index d3646a33074..42703390c27 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/curly-components-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/curly-components-test.js @@ -1459,7 +1459,7 @@ moduleFor( }, }), template: ` - + `, }); @@ -3151,15 +3151,13 @@ moduleFor( ) { this.registerComponent('display-toggle', { ComponentClass: Component.extend({ - actions: { - show() { - assert.ok(true, 'display-toggle show action was called'); - return true; - }, + show() { + assert.ok(true, 'display-toggle show action was called'); + return true; }, }), - template: ``, + template: ``, }); this.render(`{{display-toggle}}`, { @@ -3173,34 +3171,6 @@ moduleFor( runTask(() => this.$('button').click()); } - ['@test returning `true` from an action bubbles to the `target` if specified'](assert) { - assert.expect(4); - - this.registerComponent('display-toggle', { - ComponentClass: Component.extend({ - actions: { - show() { - assert.ok(true, 'display-toggle show action was called'); - return true; - }, - }, - }), - - template: ``, - }); - - this.render(`{{display-toggle target=this}}`, { - send(actionName) { - assert.ok(true, 'send should be called when action is "subscribed" to'); - assert.equal(actionName, 'show'); - }, - }); - - this.assertText('Show'); - - runTask(() => this.$('button').click()); - } - ['@test triggering an event only attempts to invoke an identically named method, if it actually is a function (GH#15228)']( assert ) { diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/dynamic-components-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/dynamic-components-test.js index 7cf3d5aae22..e0055584fdf 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/dynamic-components-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/dynamic-components-test.js @@ -452,14 +452,12 @@ moduleFor( let actionTriggered = 0; this.registerComponent('outer-component', { template: - '{{#component this.componentName somethingClicked=(action "mappedAction")}}arepas!{{/component}}', + '{{#component this.componentName somethingClicked=this.mappedAction}}arepas!{{/component}}', ComponentClass: Component.extend({ classNames: 'outer-component', componentName: 'inner-component', - actions: { - mappedAction() { - actionTriggered++; - }, + mappedAction() { + actionTriggered++; }, }), }); 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 88f20df47da..08041ea29e5 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 @@ -285,12 +285,10 @@ moduleFor( ) { assert.expect(2); - this.render(``, { - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - assert.ok(event instanceof Event, 'Native event was passed'); - }, + this.render(``, { + foo(value, event) { + assert.ok(true, 'action was triggered'); + assert.ok(event instanceof Event, 'Native event was passed'); }, }); @@ -300,14 +298,12 @@ moduleFor( } ['@test sends `insert-newline` when is pressed'](assert) { - assert.expect(2); + assert.expect(3); - this.render(``, { - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - assert.ok(event instanceof Event, 'Native event was passed'); - }, + this.render(``, { + foo(value, event) { + assert.ok(true, 'action was triggered'); + assert.ok(event instanceof Event, 'Native event was passed'); }, }); @@ -316,17 +312,15 @@ moduleFor( }); } - ['@test sends an action with `` when is pressed']( + ['@test sends an action with `` when is pressed']( assert ) { assert.expect(2); - this.render(``, { - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - assert.ok(event instanceof Event, 'Native event was passed'); - }, + this.render(``, { + foo(value, event) { + assert.ok(true, 'action was triggered'); + assert.ok(event instanceof Event, 'Native event was passed'); }, }); 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 1e8c4589cd9..ea0b55727a0 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 @@ -154,12 +154,10 @@ moduleFor( ) { assert.expect(2); - this.render(`{{input enter=(action 'foo')}}`, { - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - assert.ok(event instanceof Event, 'Native event was passed'); - }, + this.render(`{{input enter=this.foo}}`, { + foo(value, event) { + assert.ok(true, 'action was triggered'); + assert.ok(event instanceof Event, 'Native event was passed'); }, }); @@ -171,12 +169,10 @@ moduleFor( ['@test sends `insert-newline` when is pressed'](assert) { assert.expect(2); - this.render(`{{input insert-newline=(action 'foo')}}`, { - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - assert.ok(event instanceof Event, 'Native event was passed'); - }, + this.render(`{{input insert-newline=this.foo}}`, { + foo(value, event) { + assert.ok(true, 'action was triggered'); + assert.ok(event instanceof Event, 'Native event was passed'); }, }); @@ -185,17 +181,15 @@ moduleFor( }); } - ['@test sends an action with `{{input escape-press=(action "foo")}}` when is pressed']( + ['@test sends an action with `{{input escape-press=this.foo}}` when is pressed']( assert ) { assert.expect(2); - this.render(`{{input escape-press=(action 'foo')}}`, { - actions: { - foo(value, event) { - assert.ok(true, 'action was triggered'); - assert.ok(event instanceof Event, 'Native event was passed'); - }, + this.render(`{{input escape-press=this.foo}}`, { + foo(value, event) { + assert.ok(true, 'action was triggered'); + assert.ok(event instanceof Event, 'Native event was passed'); }, }); diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/link-to/routing-angle-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/link-to/routing-angle-test.js index 259cfe15b6d..e0b50279cb3 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/link-to/routing-angle-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/link-to/routing-angle-test.js @@ -1030,7 +1030,7 @@ moduleFor( this.addTemplate( 'about', ` -
+
About
{{outlet}} diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/link-to/routing-curly-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/link-to/routing-curly-test.js index 86deedf092f..ab1d8e0a9e1 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/link-to/routing-curly-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/link-to/routing-curly-test.js @@ -1100,7 +1100,7 @@ moduleFor( this.addTemplate( 'about', ` -
+
{{#link-to route='about.contact'}}About{{/link-to}}
{{outlet}} diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/tracked-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/tracked-test.js index 8a17d5d007b..1975a167925 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/tracked-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/tracked-test.js @@ -149,7 +149,7 @@ moduleFor( this.registerComponent('counter', { ComponentClass: CountComponent, - template: '', + template: '', }); this.render(''); @@ -172,7 +172,7 @@ moduleFor( this.registerComponent('counter', { ComponentClass: CountComponent, - template: '', + template: '', }); this.render(''); @@ -199,7 +199,7 @@ moduleFor( this.registerComponent('counter', { ComponentClass: CountComponent, - template: '', + template: '', }); this.render(''); @@ -230,7 +230,7 @@ moduleFor( this.registerComponent('counter', { ComponentClass: CountComponent, - template: '', + template: '', }); this.render(''); @@ -254,7 +254,7 @@ moduleFor( this.registerComponent('num-list', { ComponentClass: NumListComponent, template: strip` - `, @@ -286,7 +286,7 @@ moduleFor( this.registerComponent('counter', { ComponentClass: CountComponent, - template: '', + template: '', }); this.render(''); @@ -323,7 +323,7 @@ moduleFor( this.registerComponent('counter', { ComponentClass: CountComponent, - template: '', + template: '', }); this.render(''); @@ -360,7 +360,7 @@ moduleFor( this.registerComponent('counter', { ComponentClass: CountComponent, - template: '', + template: '', }); this.render(''); @@ -407,7 +407,7 @@ moduleFor( ComponentClass: ChildComponent, template: strip`
{{this.person.full}}
- + `, }); @@ -442,7 +442,7 @@ moduleFor( this.registerComponent('person', { ComponentClass: PersonComponent, template: strip` - {{yield this.full (action this.updatePerson)}} + {{yield this.full this.updatePerson}} `, }); @@ -485,7 +485,7 @@ moduleFor( this.registerComponent('person', { ComponentClass: PersonComponent, template: strip` - {{yield this.person (action this.updatePerson)}} + {{yield this.person this.updatePerson}} `, }); @@ -617,7 +617,7 @@ moduleFor( this.registerComponent('inner', { ComponentClass: InnerComponent, - template: '', + template: '', }); this.render('', { diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/utils-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/utils-test.js index af536c1d7b7..37039f85534 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/utils-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/utils-test.js @@ -42,10 +42,8 @@ moduleFor( let ToggleController = Controller.extend({ isExpanded: true, - actions: { - toggle: function () { - this.toggleProperty('isExpanded'); - }, + toggle: function () { + this.toggleProperty('isExpanded'); }, }); @@ -64,7 +62,7 @@ moduleFor( {{/x-toggle}} {{/x-toggle}} - + {{#if this.isExpanded}} {{x-toggle id="root-3"}} @@ -94,7 +92,7 @@ moduleFor( {{/x-toggle}} {{/x-toggle}} - + {{#if this.isExpanded}} {{x-toggle id="root-6"}} diff --git a/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js b/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js index c665be30561..02a9263b2d1 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js @@ -142,7 +142,7 @@ moduleFor( receivedEvent = event; }, }), - template: ``, + template: ``, }); this.render(`{{x-bar}}`); @@ -161,7 +161,7 @@ moduleFor( receivedEvent = event; }, }), - template: ``, + template: ``, }); this.render(`{{x-bar}}`); diff --git a/packages/@ember/-internals/glimmer/tests/integration/helpers/closure-action-test.js b/packages/@ember/-internals/glimmer/tests/integration/helpers/closure-action-test.js deleted file mode 100644 index 491ec966116..00000000000 --- a/packages/@ember/-internals/glimmer/tests/integration/helpers/closure-action-test.js +++ /dev/null @@ -1,1018 +0,0 @@ -import { RenderingTestCase, moduleFor, strip, runTask } from 'internal-test-helpers'; - -import { _getCurrentRunLoop } from '@ember/runloop'; -import { set, computed } from '@ember/object'; - -import { Component } from '../../utils/helpers'; - -moduleFor( - 'Helpers test: closure {{action}}', - class extends RenderingTestCase { - ['@test action should be called']() { - let outerActionCalled = false; - let component; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - component = this; - }, - fireAction() { - this.attrs.submit(); - }, - }); - - let OuterComponent = Component.extend({ - outerSubmit() { - outerActionCalled = true; - }, - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: '{{inner-component submit=(action this.outerSubmit)}}', - }); - - this.render('{{outer-component}}'); - - runTask(() => { - component.fireAction(); - }); - - this.assert.ok(outerActionCalled, 'the action was called'); - } - - ['@test an error is triggered when bound action function is undefined']() { - this.registerComponent('inner-component', { - template: 'inner', - }); - this.registerComponent('outer-component', { - template: '{{inner-component submit=(action this.somethingThatIsUndefined)}}', - }); - - expectAssertion(() => { - this.render('{{outer-component}}'); - }, /Action passed is null or undefined in \(action[^)]*\) from .*\./); - } - - ['@test an error is triggered when bound action being passed in is a non-function']() { - this.registerComponent('inner-component', { - template: 'inner', - }); - this.registerComponent('outer-component', { - ComponentClass: Component.extend({ - nonFunctionThing: {}, - }), - template: '{{inner-component submit=(action this.nonFunctionThing)}}', - }); - - expectAssertion(() => { - this.render('{{outer-component}}'); - }, /An action could not be made for `.*` in .*\. Please confirm that you are using either a quoted action name \(i\.e\. `\(action '.*'\)`\) or a function available in .*\./); - } - - ['@test [#12718] a nice error is shown when a bound action function is undefined and it is passed as @foo']() { - this.registerComponent('inner-component', { - template: - '', - }); - - this.registerComponent('outer-component', { - template: '{{inner-component}}', - }); - - expectAssertion(() => { - this.render('{{outer-component}}'); - }, /Action passed is null or undefined in \(action[^)]*\) from .*\./); - } - - ['@test action value is returned']() { - let expectedValue = 'terrible tom'; - let returnedValue; - let innerComponent; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - fireAction() { - returnedValue = this.attrs.submit(); - }, - }); - - let OuterComponent = Component.extend({ - outerSubmit() { - return expectedValue; - }, - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: '{{inner-component submit=(action this.outerSubmit)}}', - }); - - this.render('{{outer-component}}'); - - runTask(() => { - innerComponent.fireAction(); - }); - - this.assert.equal(returnedValue, expectedValue, 'action can return to caller'); - } - - ['@test action should be called on the correct scope']() { - let innerComponent; - let outerComponent; - let actualComponent; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - fireAction() { - this.attrs.submit(); - }, - }); - - let OuterComponent = Component.extend({ - init() { - this._super(...arguments); - outerComponent = this; - }, - isOuterComponent: true, - outerSubmit() { - actualComponent = this; - }, - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: '{{inner-component submit=(action this.outerSubmit)}}', - }); - - this.render('{{outer-component}}'); - - runTask(() => { - innerComponent.fireAction(); - }); - - this.assert.equal(actualComponent, outerComponent, 'action has the correct context'); - this.assert.ok(actualComponent.isOuterComponent, 'action has the correct context'); - } - - ['@test arguments to action are passed, curry']() { - let first = 'mitch'; - let second = 'martin'; - let third = 'matt'; - let fourth = 'wacky wycats'; - - let innerComponent; - let actualArgs; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - fireAction() { - this.attrs.submit(fourth); - }, - }); - - let OuterComponent = Component.extend({ - third, - outerSubmit() { - actualArgs = [...arguments]; - }, - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: `{{inner-component submit=(action (action this.outerSubmit "${first}") "${second}" this.third)}}`, - }); - - this.render('{{outer-component}}'); - - runTask(() => { - innerComponent.fireAction(); - }); - - this.assert.deepEqual( - actualArgs, - [first, second, third, fourth], - 'action has the correct args' - ); - } - - ['@test `this` can be passed as an argument']() { - let value = {}; - let component; - let innerComponent; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - fireAction() { - this.attrs.submit(); - }, - }); - - let OuterComponent = Component.extend({ - init() { - this._super(...arguments); - component = this; - }, - actions: { - outerAction(incomingValue) { - value = incomingValue; - }, - }, - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: '{{inner-component submit=(action "outerAction" this)}}', - }); - - this.render('{{outer-component}}'); - - runTask(() => { - innerComponent.fireAction(); - }); - - this.assert.strictEqual(value, component, 'the component is passed at `this`'); - } - - ['@test arguments to action are bound']() { - let value = 'lazy leah'; - - let innerComponent; - let outerComponent; - let actualArg; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - fireAction() { - this.attrs.submit(); - }, - }); - - let OuterComponent = Component.extend({ - init() { - this._super(...arguments); - outerComponent = this; - }, - value: '', - outerSubmit(incomingValue) { - actualArg = incomingValue; - }, - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: `{{inner-component submit=(action this.outerSubmit this.value)}}`, - }); - - this.render('{{outer-component}}'); - - runTask(() => { - innerComponent.fireAction(); - }); - - this.assert.strictEqual(actualArg, '', 'action has the correct first arg'); - - runTask(() => { - outerComponent.set('value', value); - }); - - runTask(() => { - innerComponent.fireAction(); - }); - - this.assert.strictEqual(actualArg, value, 'action has the correct first arg'); - } - - ['@test array arguments are passed correctly to action']() { - let first = 'foo'; - let second = [3, 5]; - let third = [4, 9]; - - let actualFirst; - let actualSecond; - let actualThird; - - let innerComponent; - let outerComponent; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - fireAction() { - this.attrs.submit(second, third); - }, - }); - - let OuterComponent = Component.extend({ - init() { - this._super(...arguments); - outerComponent = this; - }, - outerSubmit(incomingFirst, incomingSecond, incomingThird) { - actualFirst = incomingFirst; - actualSecond = incomingSecond; - actualThird = incomingThird; - }, - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: `{{inner-component submit=(action this.outerSubmit this.first)}}`, - }); - - this.render('{{outer-component}}'); - - runTask(() => { - outerComponent.set('first', first); - outerComponent.set('second', second); - }); - - runTask(() => { - innerComponent.fireAction(); - }); - - this.assert.equal(actualFirst, first, 'action has the correct first arg'); - this.assert.equal(actualSecond, second, 'action has the correct second arg'); - this.assert.equal(actualThird, third, 'action has the correct third arg'); - } - - ['@test mut values can be wrapped in actions, are settable']() { - let newValue = 'trollin trek'; - - let innerComponent; - let outerComponent; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - fireAction() { - this.attrs.submit(newValue); - }, - }); - - let OuterComponent = Component.extend({ - init() { - this._super(...arguments); - outerComponent = this; - }, - outerMut: 'patient peter', - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: `{{inner-component submit=(action (mut this.outerMut))}}`, - }); - - this.render('{{outer-component}}'); - - runTask(() => { - innerComponent.fireAction(); - }); - - this.assert.equal(outerComponent.get('outerMut'), newValue, 'mut value is set'); - } - - ['@test mut values can be wrapped in actions, are settable with a curry']() { - let newValue = 'trollin trek'; - - let innerComponent; - let outerComponent; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - fireAction() { - this.attrs.submit(); - }, - }); - - let OuterComponent = Component.extend({ - init() { - this._super(...arguments); - outerComponent = this; - }, - outerMut: 'patient peter', - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: `{{inner-component submit=(action (mut this.outerMut) '${newValue}')}}`, - }); - - this.render('{{outer-component}}'); - - runTask(() => { - innerComponent.fireAction(); - }); - - this.assert.equal(outerComponent.get('outerMut'), newValue, 'mut value is set'); - } - - ['@test action can create closures over actions']() { - let first = 'raging robert'; - let second = 'mild machty'; - let returnValue = 'butch brian'; - - let actualFirst; - let actualSecond; - let actualReturnedValue; - - let innerComponent; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - fireAction() { - actualReturnedValue = this.attrs.submit(second); - }, - }); - - let OuterComponent = Component.extend({ - actions: { - outerAction(incomingFirst, incomingSecond) { - actualFirst = incomingFirst; - actualSecond = incomingSecond; - return returnValue; - }, - }, - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: `{{inner-component submit=(action 'outerAction' '${first}')}}`, - }); - - this.render('{{outer-component}}'); - - runTask(() => { - innerComponent.fireAction(); - }); - - this.assert.equal(actualReturnedValue, returnValue, 'return value is present'); - this.assert.equal(actualFirst, first, 'first argument is correct'); - this.assert.equal(actualSecond, second, 'second argument is correct'); - } - - ['@test provides a helpful error if an action is not present']() { - let InnerComponent = Component.extend({}); - - let OuterComponent = Component.extend({ - actions: { - something() { - // this is present to ensure `actions` hash is present - // a different error is triggered if `actions` is missing - // completely - }, - }, - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: `{{inner-component submit=(action 'doesNotExist')}}`, - }); - - expectAssertion(() => { - this.render('{{outer-component}}'); - }, /An action named 'doesNotExist' was not found in /); - } - - ['@test provides a helpful error if actions hash is not present']() { - let InnerComponent = Component.extend({}); - - let OuterComponent = Component.extend({}); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: `{{inner-component submit=(action 'doesNotExist')}}`, - }); - - expectAssertion(() => { - this.render('{{outer-component}}'); - }, /An action named 'doesNotExist' was not found in /); - } - - ['@test action can create closures over actions with target']() { - let innerComponent; - let actionCalled = false; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - fireAction() { - this.attrs.submit(); - }, - }); - - let OuterComponent = Component.extend({ - otherComponent: computed(function () { - return { - actions: { - outerAction() { - actionCalled = true; - }, - }, - }; - }), - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: `{{inner-component submit=(action 'outerAction' target=this.otherComponent)}}`, - }); - - this.render('{{outer-component}}'); - - runTask(() => { - innerComponent.fireAction(); - }); - - this.assert.ok(actionCalled, 'action called on otherComponent'); - } - - ['@test value can be used with action over actions']() { - let newValue = 'yelping yehuda'; - - let innerComponent; - let actualValue; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - fireAction() { - this.attrs.submit({ - readProp: newValue, - }); - }, - }); - - let OuterComponent = Component.extend({ - outerContent: { - readProp: newValue, - }, - actions: { - outerAction(incomingValue) { - actualValue = incomingValue; - }, - }, - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: `{{inner-component submit=(action 'outerAction' value="readProp")}}`, - }); - - this.render('{{outer-component}}'); - - runTask(() => { - innerComponent.fireAction(); - }); - - this.assert.equal(actualValue, newValue, 'value is read'); - } - - ['@test action will read the value of a first property']() { - let newValue = 'irate igor'; - - let innerComponent; - let actualValue; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - fireAction() { - this.attrs.submit({ - readProp: newValue, - }); - }, - }); - - let OuterComponent = Component.extend({ - outerAction(incomingValue) { - actualValue = incomingValue; - }, - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: `{{inner-component submit=(action this.outerAction value="readProp")}}`, - }); - - this.render('{{outer-component}}'); - - runTask(() => { - innerComponent.fireAction(); - }); - - this.assert.equal(actualValue, newValue, 'property is read'); - } - - ['@test action will read the value of a curried first argument property']() { - let newValue = 'kissing kris'; - - let innerComponent; - let actualValue; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - fireAction() { - this.attrs.submit(); - }, - }); - - let OuterComponent = Component.extend({ - objectArgument: { - readProp: newValue, - }, - outerAction(incomingValue) { - actualValue = incomingValue; - }, - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: `{{inner-component submit=(action this.outerAction this.objectArgument value="readProp")}}`, - }); - - this.render('{{outer-component}}'); - - runTask(() => { - innerComponent.fireAction(); - }); - - this.assert.equal(actualValue, newValue, 'property is read'); - } - - ['@test action closure does not get auto-mut wrapped'](assert) { - let first = 'raging robert'; - let second = 'mild machty'; - let returnValue = 'butch brian'; - - let innerComponent; - let actualFirst; - let actualSecond; - let actualReturnedValue; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - fireAction() { - this.get('submit')(second); - this.get('attrs-submit')(second); - let attrsSubmitReturnValue = this.attrs['attrs-submit'](second); - let submitReturnValue = this.attrs.submit(second); - - assert.equal( - attrsSubmitReturnValue, - submitReturnValue, - 'both attrs.foo and foo should behave the same' - ); - - return submitReturnValue; - }, - }); - - let MiddleComponent = Component.extend({}); - - let OuterComponent = Component.extend({ - actions: { - outerAction(incomingFirst, incomingSecond) { - actualFirst = incomingFirst; - actualSecond = incomingSecond; - return returnValue; - }, - }, - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('middle-component', { - ComponentClass: MiddleComponent, - template: `{{inner-component attrs-submit=@submit submit=this.submit}}`, - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: `{{middle-component submit=(action 'outerAction' '${first}')}}`, - }); - - this.render('{{outer-component}}'); - - runTask(() => { - actualReturnedValue = innerComponent.fireAction(); - }); - - this.assert.equal(actualFirst, first, 'first argument is correct'); - this.assert.equal(actualSecond, second, 'second argument is correct'); - this.assert.equal(actualReturnedValue, returnValue, 'return value is present'); - } - - ['@test action should be called within a run loop']() { - let innerComponent; - let capturedRunLoop; - - let InnerComponent = Component.extend({ - init() { - this._super(...arguments); - innerComponent = this; - }, - fireAction() { - this.attrs.submit(); - }, - }); - - let OuterComponent = Component.extend({ - actions: { - submit() { - capturedRunLoop = _getCurrentRunLoop(); - }, - }, - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: 'inner', - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: `{{inner-component submit=(action 'submit')}}`, - }); - - this.render('{{outer-component}}'); - - runTask(() => { - innerComponent.fireAction(); - }); - - this.assert.ok(capturedRunLoop, 'action is called within a run loop'); - } - - ['@test closure action with `(mut undefinedThing)` works properly [GH#13959]']() { - let component; - - let ExampleComponent = Component.extend({ - label: undefined, - init() { - this._super(...arguments); - component = this; - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: - '', - }); - - this.render('{{example-component}}'); - - this.assertText('Click me'); - - this.assertStableRerender(); - - runTask(() => { - this.$('button').click(); - }); - - this.assertText('Clicked!'); - - runTask(() => { - component.set('label', 'Dun clicked'); - }); - - this.assertText('Dun clicked'); - - runTask(() => { - this.$('button').click(); - }); - - this.assertText('Clicked!'); - - runTask(() => { - component.set('label', undefined); - }); - - this.assertText('Click me'); - } - - ['@test closure actions does not cause component hooks to fire unnecessarily [GH#14305] [GH#14654]']( - assert - ) { - let clicked = 0; - let didReceiveAttrsFired = 0; - - let ClickMeComponent = Component.extend({ - tagName: 'button', - - click() { - this.get('onClick').call(undefined, ++clicked); - }, - - didReceiveAttrs() { - didReceiveAttrsFired++; - }, - }); - - this.registerComponent('click-me', { - ComponentClass: ClickMeComponent, - }); - - let outer; - - let OuterComponent = Component.extend({ - clicked: 0, - - actions: { - 'on-click': function () { - this.incrementProperty('clicked'); - }, - }, - - init() { - this._super(); - outer = this; - this.set('onClick', () => this.incrementProperty('clicked')); - }, - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: strip` -
clicked: {{this.clicked}}; foo: {{this.foo}}
- - {{click-me id="string-action" onClick=(action "on-click")}} - {{click-me id="function-action" onClick=(action this.onClick)}} - {{click-me id="mut-action" onClick=(action (mut this.clicked))}} - `, - }); - - this.render('{{outer-component foo=this.foo}}', { foo: 1 }); - - this.assertText('clicked: 0; foo: 1'); - - assert.equal(didReceiveAttrsFired, 3); - - runTask(() => this.rerender()); - - this.assertText('clicked: 0; foo: 1'); - - assert.equal(didReceiveAttrsFired, 3); - - runTask(() => set(this.context, 'foo', 2)); - - this.assertText('clicked: 0; foo: 2'); - - assert.equal(didReceiveAttrsFired, 3); - - runTask(() => this.$('#string-action').click()); - - this.assertText('clicked: 1; foo: 2'); - - assert.equal(didReceiveAttrsFired, 3); - - runTask(() => this.$('#function-action').click()); - - this.assertText('clicked: 2; foo: 2'); - - assert.equal(didReceiveAttrsFired, 3); - - runTask(() => - set(outer, 'onClick', function () { - outer.incrementProperty('clicked'); - }) - ); - - this.assertText('clicked: 2; foo: 2'); - - assert.equal(didReceiveAttrsFired, 3); - - runTask(() => this.$('#function-action').click()); - - this.assertText('clicked: 3; foo: 2'); - - assert.equal(didReceiveAttrsFired, 3); - - runTask(() => this.$('#mut-action').click()); - - this.assertText('clicked: 4; foo: 2'); - - assert.equal(didReceiveAttrsFired, 3); - } - } -); 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 deleted file mode 100644 index d5b0728a0ed..00000000000 --- a/packages/@ember/-internals/glimmer/tests/integration/helpers/element-action-test.js +++ /dev/null @@ -1,1590 +0,0 @@ -import { RenderingTestCase, moduleFor, strip, runTask } from 'internal-test-helpers'; - -import EmberObject, { set } from '@ember/object'; -import { A as emberA } from '@ember/array'; -import { ActionManager } from '@ember/-internals/views'; - -import { Component } from '../../utils/helpers'; - -function getActionAttributes(element) { - let attributes = element.attributes; - let actionAttrs = []; - - for (let i = 0; i < attributes.length; i++) { - let attr = attributes.item(i); - - if (attr.name.indexOf('data-ember-action-') === 0) { - actionAttrs.push(attr.name); - } - } - - return actionAttrs; -} - -function getActionIds(element) { - return getActionAttributes(element).map((attribute) => - attribute.slice('data-ember-action-'.length) - ); -} - -moduleFor( - 'Helpers test: element action', - class extends RenderingTestCase { - ['@test it can call an action on its enclosing component']() { - let fooCallCount = 0; - - let ExampleComponent = Component.extend({ - actions: { - foo() { - fooCallCount++; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: '', - }); - - this.render('{{example-component}}'); - - this.assert.equal(fooCallCount, 0, 'foo has not been called'); - - runTask(() => this.rerender()); - - this.assert.equal(fooCallCount, 0, 'foo has not been called'); - - runTask(() => { - this.$('button').click(); - }); - - this.assert.equal(fooCallCount, 1, 'foo has been called 1 time'); - - runTask(() => { - this.$('button').click(); - }); - - this.assert.equal(fooCallCount, 2, 'foo has been called 2 times'); - } - - ['@test it can call an action with parameters']() { - let fooArgs = []; - let component; - - let ExampleComponent = Component.extend({ - member: 'a', - init() { - this._super(...arguments); - component = this; - }, - actions: { - foo(thing) { - fooArgs.push(thing); - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: '', - }); - - this.render('{{example-component}}'); - - this.assert.deepEqual(fooArgs, [], 'foo has not been called'); - - runTask(() => this.rerender()); - - this.assert.deepEqual(fooArgs, [], 'foo has not been called'); - - runTask(() => { - this.$('button').click(); - }); - - this.assert.deepEqual(fooArgs, ['a'], 'foo has not been called'); - - runTask(() => { - component.set('member', 'b'); - }); - - runTask(() => { - this.$('button').click(); - }); - - this.assert.deepEqual(fooArgs, ['a', 'b'], 'foo has been called with an updated value'); - } - - ['@test it should output a marker attribute with a guid']() { - this.render(''); - - let button = this.$('button'); - - let attributes = getActionAttributes(button[0]); - - this.assert.ok( - button.attr('data-ember-action').match(''), - 'An empty data-ember-action attribute was added' - ); - this.assert.ok( - attributes[0].match(/data-ember-action-\d+/), - 'A data-ember-action-xyz attribute with a guid was added' - ); - } - - ['@test it should allow alternative events to be handled']() { - let showCalled = false; - - let ExampleComponent = Component.extend({ - actions: { - show() { - showCalled = true; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: '
', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('#show').trigger('mouseup'); - }); - - this.assert.ok(showCalled, 'show action was called on mouseUp'); - } - - ['@test inside a yield, the target points at the original target']() { - let targetWatted = false; - let innerWatted = false; - - let TargetComponent = Component.extend({ - actions: { - wat() { - targetWatted = true; - }, - }, - }); - - let InnerComponent = Component.extend({ - actions: { - wat() { - innerWatted = true; - }, - }, - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: '{{yield}}', - }); - - this.registerComponent('target-component', { - ComponentClass: TargetComponent, - template: strip` - {{#inner-component}} - - {{/inner-component}} - `, - }); - - this.render('{{target-component}}'); - - runTask(() => { - this.$('button').click(); - }); - - this.assert.ok(targetWatted, 'the correct target was watted'); - this.assert.notOk(innerWatted, 'the inner target was not watted'); - } - - ['@test it should allow a target to be specified']() { - let targetWatted = false; - - let TargetComponent = Component.extend({ - actions: { - wat() { - targetWatted = true; - }, - }, - }); - - let OtherComponent = Component.extend({}); - - this.registerComponent('target-component', { - ComponentClass: TargetComponent, - template: '{{yield this}}', - }); - - this.registerComponent('other-component', { - ComponentClass: OtherComponent, - template: 'Wat?', - }); - - this.render(strip` - {{#target-component as |parent|}} - {{other-component anotherTarget=parent}} - {{/target-component}} - `); - - runTask(() => { - this.$('a').click(); - }); - - this.assert.equal(targetWatted, true, 'the specified target was watted'); - } - - ['@test it should lazily evaluate the target']() { - let firstEdit = 0; - let secondEdit = 0; - let component; - - let first = { - edit() { - firstEdit++; - }, - }; - - let second = { - edit() { - secondEdit++; - }, - }; - - let ExampleComponent = Component.extend({ - init() { - this._super(...arguments); - component = this; - }, - theTarget: first, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: 'Edit', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('a').click(); - }); - - this.assert.equal(firstEdit, 1); - - runTask(() => { - set(component, 'theTarget', second); - }); - - runTask(() => { - this.$('a').click(); - }); - - this.assert.equal(firstEdit, 1); - this.assert.equal(secondEdit, 1); - } - - ['@test it should register an event handler']() { - let editHandlerWasCalled = false; - let shortcutHandlerWasCalled = false; - - let ExampleComponent = Component.extend({ - actions: { - edit() { - editHandlerWasCalled = true; - }, - shortcut() { - shortcutHandlerWasCalled = true; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: - 'click me
click me too
', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('a[data-ember-action]').trigger('click', { altKey: true }); - }); - - this.assert.equal(editHandlerWasCalled, true, 'the event handler was called'); - - runTask(() => { - this.$('div[data-ember-action]').trigger('click', { ctrlKey: true }); - }); - - this.assert.equal( - shortcutHandlerWasCalled, - true, - 'the "any" shortcut\'s event handler was called' - ); - } - - ['@test it handles whitelisted bound modifier keys']() { - let editHandlerWasCalled = false; - let shortcutHandlerWasCalled = false; - - let ExampleComponent = Component.extend({ - altKey: 'alt', - anyKey: 'any', - actions: { - edit() { - editHandlerWasCalled = true; - }, - shortcut() { - shortcutHandlerWasCalled = true; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: - 'click me
click me too
', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('a[data-ember-action]').trigger('click', { altKey: true }); - }); - - this.assert.equal(editHandlerWasCalled, true, 'the event handler was called'); - - runTask(() => { - this.$('div[data-ember-action]').trigger('click', { ctrlKey: true }); - }); - - this.assert.equal( - shortcutHandlerWasCalled, - true, - 'the "any" shortcut\'s event handler was called' - ); - } - - ['@test it handles whitelisted bound modifier keys with current value']() { - let editHandlerWasCalled = false; - let component; - - let ExampleComponent = Component.extend({ - init() { - this._super(...arguments); - component = this; - }, - acceptedKeys: 'alt', - actions: { - edit() { - editHandlerWasCalled = true; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: 'click me', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('a[data-ember-action]').trigger('click', { altKey: true }); - }); - - this.assert.equal(editHandlerWasCalled, true, 'the event handler was called'); - - editHandlerWasCalled = false; - - runTask(() => { - component.set('acceptedKeys', ''); - }); - - runTask(() => { - this.$('div[data-ember-action]').click(); - }); - - this.assert.equal(editHandlerWasCalled, false, 'the event handler was not called'); - } - - ['@test should be able to use action more than once for the same event within a view']() { - let editHandlerWasCalled = false; - let deleteHandlerWasCalled = false; - let originalHandlerWasCalled = false; - let component; - - let ExampleComponent = Component.extend({ - init() { - this._super(...arguments); - component = this; - }, - actions: { - edit() { - editHandlerWasCalled = true; - }, - delete() { - deleteHandlerWasCalled = true; - }, - }, - click() { - originalHandlerWasCalled = true; - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: - 'editdelete', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('#edit').click(); - }); - - this.assert.equal(editHandlerWasCalled, true, 'the edit action was called'); - this.assert.equal(deleteHandlerWasCalled, false, 'the delete action was not called'); - this.assert.equal( - originalHandlerWasCalled, - true, - 'the click handler was called (due to bubbling)' - ); - - editHandlerWasCalled = deleteHandlerWasCalled = originalHandlerWasCalled = false; - - runTask(() => { - this.$('#delete').click(); - }); - - this.assert.equal(editHandlerWasCalled, false, 'the edit action was not called'); - this.assert.equal(deleteHandlerWasCalled, true, 'the delete action was called'); - this.assert.equal( - originalHandlerWasCalled, - true, - 'the click handler was called (due to bubbling)' - ); - - editHandlerWasCalled = deleteHandlerWasCalled = originalHandlerWasCalled = false; - - runTask(() => { - this.wrap(component.element).click(); - }); - - this.assert.equal(editHandlerWasCalled, false, 'the edit action was not called'); - this.assert.equal(deleteHandlerWasCalled, false, 'the delete action was not called'); - this.assert.equal(originalHandlerWasCalled, true, 'the click handler was called'); - } - - ['@test the event should not bubble if `bubbles=false` is passed']() { - let editHandlerWasCalled = false; - let deleteHandlerWasCalled = false; - let originalHandlerWasCalled = false; - let component; - - let ExampleComponent = Component.extend({ - init() { - this._super(...arguments); - component = this; - }, - actions: { - edit() { - editHandlerWasCalled = true; - }, - delete() { - deleteHandlerWasCalled = true; - }, - }, - click() { - originalHandlerWasCalled = true; - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: - 'editdelete', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('#edit').click(); - }); - - this.assert.equal(editHandlerWasCalled, true, 'the edit action was called'); - this.assert.equal(deleteHandlerWasCalled, false, 'the delete action was not called'); - this.assert.equal(originalHandlerWasCalled, false, 'the click handler was not called'); - - editHandlerWasCalled = deleteHandlerWasCalled = originalHandlerWasCalled = false; - - runTask(() => { - this.$('#delete').click(); - }); - - this.assert.equal(editHandlerWasCalled, false, 'the edit action was not called'); - this.assert.equal(deleteHandlerWasCalled, true, 'the delete action was called'); - this.assert.equal(originalHandlerWasCalled, false, 'the click handler was not called'); - - editHandlerWasCalled = deleteHandlerWasCalled = originalHandlerWasCalled = false; - - runTask(() => { - this.wrap(component.element).click(); - }); - - this.assert.equal(editHandlerWasCalled, false, 'the edit action was not called'); - this.assert.equal(deleteHandlerWasCalled, false, 'the delete action was not called'); - this.assert.equal(originalHandlerWasCalled, true, 'the click handler was called'); - } - - ['@test the event should not bubble if `bubbles=false` is passed bound']() { - let editHandlerWasCalled = false; - let deleteHandlerWasCalled = false; - let originalHandlerWasCalled = false; - let component; - - let ExampleComponent = Component.extend({ - init() { - this._super(...arguments); - component = this; - }, - isFalse: false, - actions: { - edit() { - editHandlerWasCalled = true; - }, - delete() { - deleteHandlerWasCalled = true; - }, - }, - click() { - originalHandlerWasCalled = true; - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: - 'editdelete', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('#edit').click(); - }); - - this.assert.equal(editHandlerWasCalled, true, 'the edit action was called'); - this.assert.equal(deleteHandlerWasCalled, false, 'the delete action was not called'); - this.assert.equal(originalHandlerWasCalled, false, 'the click handler was not called'); - - editHandlerWasCalled = deleteHandlerWasCalled = originalHandlerWasCalled = false; - - runTask(() => { - this.$('#delete').click(); - }); - - this.assert.equal(editHandlerWasCalled, false, 'the edit action was not called'); - this.assert.equal(deleteHandlerWasCalled, true, 'the delete action was called'); - this.assert.equal(originalHandlerWasCalled, false, 'the click handler was not called'); - - editHandlerWasCalled = deleteHandlerWasCalled = originalHandlerWasCalled = false; - - runTask(() => { - this.wrap(component.element).click(); - }); - - this.assert.equal(editHandlerWasCalled, false, 'the edit action was not called'); - this.assert.equal(deleteHandlerWasCalled, false, 'the delete action was not called'); - this.assert.equal(originalHandlerWasCalled, true, 'the click handler was called'); - } - - ['@test the bubbling depends on the bound parameter']() { - let editHandlerWasCalled = false; - let originalHandlerWasCalled = false; - let component; - - let ExampleComponent = Component.extend({ - init() { - this._super(...arguments); - component = this; - }, - shouldBubble: false, - actions: { - edit() { - editHandlerWasCalled = true; - }, - }, - click() { - originalHandlerWasCalled = true; - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: 'edit', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('#edit').click(); - }); - - this.assert.equal(editHandlerWasCalled, true, 'the edit action was called'); - this.assert.equal(originalHandlerWasCalled, false, 'the click handler was not called'); - - editHandlerWasCalled = originalHandlerWasCalled = false; - - runTask(() => { - component.set('shouldBubble', true); - }); - - runTask(() => { - this.$('#edit').click(); - }); - - this.assert.equal(editHandlerWasCalled, true, 'the edit action was called'); - this.assert.equal(originalHandlerWasCalled, true, 'the click handler was called'); - } - - ['@test multiple actions with bubbles=false for same event are called but prevent bubbling']() { - let clickAction1WasCalled = false; - let clickAction2WasCalled = false; - let eventHandlerWasCalled = false; - - let ExampleComponent = Component.extend({ - actions: { - clicked1() { - clickAction1WasCalled = true; - }, - clicked2() { - clickAction2WasCalled = true; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: strip` - click me`, - click() { - eventHandlerWasCalled = true; - }, - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('a').trigger('click'); - }); - - this.assert.ok(clickAction1WasCalled, 'the first clicked action was called'); - this.assert.ok(clickAction2WasCalled, 'the second clicked action was called'); - this.assert.notOk(eventHandlerWasCalled, 'event did not bubble up'); - } - - ['@test it should work properly in an #each block']() { - let editHandlerWasCalled = false; - - let ExampleComponent = Component.extend({ - items: emberA([1, 2, 3, 4]), - actions: { - edit() { - editHandlerWasCalled = true; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: - '{{#each this.items as |item|}}click me{{/each}}', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('a').click(); - }); - - this.assert.equal(editHandlerWasCalled, true, 'the event handler was called'); - } - - ['@test it should work properly in a {{#let foo as |bar|}} block']() { - let editHandlerWasCalled = false; - - let ExampleComponent = Component.extend({ - something: { ohai: 'there' }, - actions: { - edit() { - editHandlerWasCalled = true; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: - '{{#let this.something as |somethingElse|}}click me{{/let}}', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('a').click(); - }); - - this.assert.equal(editHandlerWasCalled, true, 'the event handler was called'); - } - - ['@test it should unregister event handlers when an element action is removed'](assert) { - let ExampleComponent = Component.extend({ - actions: { - edit() {}, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: '{{#if this.isActive}}click me{{/if}}', - }); - - this.render('{{example-component isActive=this.isActive}}', { - isActive: true, - }); - - assert.equal(this.$('a[data-ember-action]').length, 1, 'The element is rendered'); - - let actionId; - - actionId = getActionIds(this.$('a[data-ember-action]')[0])[0]; - - assert.ok(ActionManager.registeredActions[actionId], 'An action is registered'); - - runTask(() => this.rerender()); - - assert.equal(this.$('a[data-ember-action]').length, 1, 'The element is still present'); - - assert.ok(ActionManager.registeredActions[actionId], 'The action is still registered'); - - runTask(() => set(this.context, 'isActive', false)); - - assert.strictEqual(this.$('a[data-ember-action]').length, 0, 'The element is removed'); - - assert.ok(!ActionManager.registeredActions[actionId], 'The action is unregistered'); - - runTask(() => set(this.context, 'isActive', true)); - - assert.equal(this.$('a[data-ember-action]').length, 1, 'The element is rendered'); - - actionId = getActionIds(this.$('a[data-ember-action]')[0])[0]; - - assert.ok(ActionManager.registeredActions[actionId], 'A new action is registered'); - } - - ['@test it should capture events from child elements and allow them to trigger the action']() { - let editHandlerWasCalled = false; - - let ExampleComponent = Component.extend({ - actions: { - edit() { - editHandlerWasCalled = true; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: '
', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('button').click(); - }); - - this.assert.ok( - editHandlerWasCalled, - 'event on a child target triggered the action of its parent' - ); - } - - ['@test it should allow bubbling of events from action helper to original parent event']() { - let editHandlerWasCalled = false; - let originalHandlerWasCalled = false; - - let ExampleComponent = Component.extend({ - actions: { - edit() { - editHandlerWasCalled = true; - }, - }, - click() { - originalHandlerWasCalled = true; - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: 'click me', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('a').click(); - }); - - this.assert.ok( - editHandlerWasCalled && originalHandlerWasCalled, - 'both event handlers were called' - ); - } - - ['@test it should not bubble an event from action helper to original parent event if `bubbles=false` is passed']() { - let editHandlerWasCalled = false; - let originalHandlerWasCalled = false; - - let ExampleComponent = Component.extend({ - actions: { - edit() { - editHandlerWasCalled = true; - }, - }, - click() { - originalHandlerWasCalled = true; - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: 'click me', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('a').click(); - }); - - this.assert.ok(editHandlerWasCalled, 'the child event handler was called'); - this.assert.notOk(originalHandlerWasCalled, 'the parent handler was not called'); - } - - ['@test it should allow "send" as the action name (#594)']() { - let sendHandlerWasCalled = false; - - let ExampleComponent = Component.extend({ - actions: { - send() { - sendHandlerWasCalled = true; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: 'click me', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('a').click(); - }); - - this.assert.ok(sendHandlerWasCalled, 'the event handler was called'); - } - - ['@test it should send the view, event, and current context to the action']() { - let passedTarget; - let passedContext; - let targetThis; - - let TargetComponent = Component.extend({ - init() { - this._super(...arguments); - targetThis = this; - }, - actions: { - edit(context) { - passedTarget = this === targetThis; - passedContext = context; - }, - }, - }); - - let aContext; - - let ExampleComponent = Component.extend({ - init() { - this._super(...arguments); - aContext = this; - }, - }); - - this.registerComponent('target-component', { - ComponentClass: TargetComponent, - template: '{{yield this}}', - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: strip` - {{#target-component as |aTarget|}} - click me - {{/target-component}} - `, - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('#edit').click(); - }); - - this.assert.ok(passedTarget, 'the action is called with the target as this'); - this.assert.strictEqual(passedContext, aContext, 'the parameter is passed along'); - } - - ['@test it should only trigger actions for the event they were registered on']() { - let editHandlerWasCalled = false; - - let ExampleComponent = Component.extend({ - actions: { - edit() { - editHandlerWasCalled = true; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: 'click me', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('a').click(); - }); - - this.assert.ok(editHandlerWasCalled, 'the event handler was called on click'); - - editHandlerWasCalled = false; - - runTask(() => { - this.$('a').trigger('mouseover'); - }); - - this.assert.notOk(editHandlerWasCalled, 'the event handler was not called on mouseover'); - } - - ['@test it should allow multiple contexts to be specified']() { - let passedContexts; - let models = [EmberObject.create(), EmberObject.create()]; - - let ExampleComponent = Component.extend({ - modelA: models[0], - modelB: models[1], - actions: { - edit(...args) { - passedContexts = args; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: '', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('button').click(); - }); - - this.assert.deepEqual( - passedContexts, - models, - 'the action was called with the passed contexts' - ); - } - - ['@test it should allow multiple contexts to be specified mixed with string args']() { - let passedContexts; - let model = EmberObject.create(); - - let ExampleComponent = Component.extend({ - model: model, - actions: { - edit(...args) { - passedContexts = args; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: '', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('button').click(); - }); - - this.assert.deepEqual( - passedContexts, - ['herp', model], - 'the action was called with the passed contexts' - ); - } - - ['@test it should not trigger action with special clicks']() { - let showCalled = false; - let component; - - let ExampleComponent = Component.extend({ - init() { - this._super(...arguments); - component = this; - }, - actions: { - show() { - showCalled = true; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: '', - }); - - this.render('{{example-component}}'); - - let assert = this.assert; - - let checkClick = (prop, value, expected) => { - showCalled = false; - let event = this.wrap(component.element) - .findAll('button') - .trigger('click', { [prop]: value })[0]; - if (expected) { - assert.ok(showCalled, `should call action with ${prop}:${value}`); - assert.ok(event.defaultPrevented, 'should prevent default'); - } else { - assert.notOk(showCalled, `should not call action with ${prop}:${value}`); - assert.notOk(event.defaultPrevented, 'should not prevent default'); - } - }; - - checkClick('ctrlKey', true, false); - checkClick('altKey', true, false); - checkClick('metaKey', true, false); - checkClick('shiftKey', true, false); - - checkClick('button', 0, true); - checkClick('button', 1, false); - checkClick('button', 2, false); - checkClick('button', 3, false); - checkClick('button', 4, false); - } - - ['@test it can trigger actions for keyboard events']() { - let showCalled = false; - - let ExampleComponent = Component.extend({ - actions: { - show() { - showCalled = true; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: '', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('input').trigger('keyup', { char: 'a', which: 65 }); - }); - - this.assert.ok(showCalled, 'the action was called with keyup'); - } - - ['@test a quoteless parameter should allow dynamic lookup of the actionName']() { - let lastAction; - let actionOrder = []; - let component; - - let ExampleComponent = Component.extend({ - init() { - this._super(...arguments); - component = this; - }, - hookMeUp: 'rock', - actions: { - rock() { - lastAction = 'rock'; - actionOrder.push('rock'); - }, - paper() { - lastAction = 'paper'; - actionOrder.push('paper'); - }, - scissors() { - lastAction = 'scissors'; - actionOrder.push('scissors'); - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: 'Whistle tips go woop woooop', - }); - - this.render('{{example-component}}'); - - let test = this; - - let testBoundAction = (propertyValue) => { - runTask(() => { - component.set('hookMeUp', propertyValue); - }); - - runTask(() => { - this.wrap(component.element).findAll('#bound-param').click(); - }); - - test.assert.ok(lastAction, propertyValue, `lastAction set to ${propertyValue}`); - }; - - testBoundAction('rock'); - testBoundAction('paper'); - testBoundAction('scissors'); - - this.assert.deepEqual( - actionOrder, - ['rock', 'paper', 'scissors'], - 'action name was looked up properly' - ); - } - - ['@test a quoteless string parameter should resolve actionName, including path']() { - let lastAction; - let actionOrder = []; - let component; - - let ExampleComponent = Component.extend({ - init() { - this._super(...arguments); - component = this; - }, - allactions: emberA([ - { title: 'Rock', name: 'rock' }, - { title: 'Paper', name: 'paper' }, - { title: 'Scissors', name: 'scissors' }, - ]), - actions: { - rock() { - lastAction = 'rock'; - actionOrder.push('rock'); - }, - paper() { - lastAction = 'paper'; - actionOrder.push('paper'); - }, - scissors() { - lastAction = 'scissors'; - actionOrder.push('scissors'); - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: - '{{#each this.allactions as |allaction|}}{{allaction.title}}{{/each}}', - }); - - this.render('{{example-component}}'); - - let test = this; - - let testBoundAction = (propertyValue) => { - runTask(() => { - this.wrap(component.element).findAll(`#${propertyValue}`).click(); - }); - - test.assert.ok(lastAction, propertyValue, `lastAction set to ${propertyValue}`); - }; - - testBoundAction('rock'); - testBoundAction('paper'); - testBoundAction('scissors'); - - this.assert.deepEqual( - actionOrder, - ['rock', 'paper', 'scissors'], - 'action name was looked up properly' - ); - } - - ['@test a quoteless function parameter should be called, including arguments']() { - let submitCalled = false; - let incomingArg; - - let arg = 'rough ray'; - - let ExampleComponent = Component.extend({ - submit(actualArg) { - incomingArg = actualArg; - submitCalled = true; - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: `Hi`, - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('a').click(); - }); - - this.assert.ok(submitCalled, 'submit function called'); - this.assert.equal(incomingArg, arg, 'argument passed'); - } - - ['@test a quoteless parameter that does not resolve to a value asserts']() { - let ExampleComponent = Component.extend({ - actions: { - ohNoeNotValid() {}, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: 'Hi', - }); - - expectAssertion(() => { - this.render('{{example-component}}'); - }, 'You specified a quoteless path, `this.ohNoeNotValid`, to the {{action}} helper ' + 'which did not resolve to an action name (a string). ' + 'Perhaps you meant to use a quoted actionName? (e.g. {{action "ohNoeNotValid"}}).'); - } - - ['@test allows multiple actions on a single element']() { - let clickActionWasCalled = false; - let doubleClickActionWasCalled = false; - - let ExampleComponent = Component.extend({ - actions: { - clicked() { - clickActionWasCalled = true; - }, - doubleClicked() { - doubleClickActionWasCalled = true; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: strip` - click me`, - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('a').trigger('click'); - }); - - this.assert.ok(clickActionWasCalled, 'the clicked action was called'); - - runTask(() => { - this.$('a').trigger('dblclick'); - }); - - this.assert.ok(doubleClickActionWasCalled, 'the doubleClicked action was called'); - } - - ['@test allows multiple actions for same event on a single element']() { - let clickAction1WasCalled = false; - let clickAction2WasCalled = false; - - let ExampleComponent = Component.extend({ - actions: { - clicked1() { - clickAction1WasCalled = true; - }, - clicked2() { - clickAction2WasCalled = true; - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: strip` - click me`, - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('a').trigger('click'); - }); - - this.assert.ok(clickAction1WasCalled, 'the first clicked action was called'); - this.assert.ok(clickAction2WasCalled, 'the second clicked action was called'); - } - - ['@test it should respect preventDefault option if provided']() { - let ExampleComponent = Component.extend({ - actions: { - show() {}, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: 'Hi', - }); - - this.render('{{example-component}}'); - - let event; - - runTask(() => { - event = this.$('a').click()[0]; - }); - - this.assert.equal(event.defaultPrevented, false, 'should not preventDefault'); - } - - ['@test it should respect preventDefault option if provided bound']() { - let component; - - let ExampleComponent = Component.extend({ - shouldPreventDefault: false, - init() { - this._super(...arguments); - component = this; - }, - actions: { - show() {}, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: 'Hi', - }); - - this.render('{{example-component}}'); - - let event; - - runTask(() => { - event = this.$('a').trigger(event)[0]; - }); - - this.assert.equal(event.defaultPrevented, false, 'should not preventDefault'); - - runTask(() => { - component.set('shouldPreventDefault', true); - event = this.$('a').trigger('click')[0]; - }); - - this.assert.equal(event.defaultPrevented, true, 'should preventDefault'); - } - - ['@test it should target the proper component when `action` is in yielded block [GH #12409]']() { - let outerActionCalled = false; - let innerClickCalled = false; - - let OuterComponent = Component.extend({ - actions: { - hey() { - outerActionCalled = true; - }, - }, - }); - - let MiddleComponent = Component.extend({}); - - let InnerComponent = Component.extend({ - click() { - innerClickCalled = true; - this.action(); - }, - }); - - this.registerComponent('outer-component', { - ComponentClass: OuterComponent, - template: strip` - {{#middle-component}} - {{inner-component action=(action "hey")}} - {{/middle-component}} - `, - }); - - this.registerComponent('middle-component', { - ComponentClass: MiddleComponent, - template: '{{yield}}', - }); - - this.registerComponent('inner-component', { - ComponentClass: InnerComponent, - template: strip` - - {{yield}} - `, - }); - - this.render('{{outer-component}}'); - - runTask(() => { - this.$('button').click(); - }); - - this.assert.ok(outerActionCalled, 'the action fired on the proper target'); - this.assert.ok(innerClickCalled, 'the click was triggered'); - } - - ['@test element action with (mut undefinedThing) works properly']() { - let component; - - let ExampleComponent = Component.extend({ - label: undefined, - init() { - this._super(...arguments); - component = this; - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: - '', - }); - - this.render('{{example-component}}'); - - this.assertText('Click me'); - - this.assertStableRerender(); - - runTask(() => { - this.$('button').click(); - }); - - this.assertText('Clicked!'); - - runTask(() => { - component.set('label', 'Dun clicked'); - }); - - this.assertText('Dun clicked'); - - runTask(() => { - this.$('button').click(); - }); - - this.assertText('Clicked!'); - - runTask(() => { - component.set('label', undefined); - }); - - this.assertText('Click me'); - } - - ['@test it supports non-registered actions [GH#14888]']() { - this.render( - ` - {{#if this.show}} - - {{/if}} - `, - { show: true } - ); - - this.assert.equal(this.$('button').text().trim(), 'Show (true)'); - // We need to focus in to simulate an actual click. - runTask(() => { - document.getElementById('ddButton').focus(); - document.getElementById('ddButton').click(); - }); - } - - ["@test action handler that shifts element attributes doesn't trigger multiple invocations"]() { - let actionCount = 0; - let ExampleComponent = Component.extend({ - selected: false, - actions: { - toggleSelected() { - actionCount++; - this.toggleProperty('selected'); - }, - }, - }); - - this.registerComponent('example-component', { - ComponentClass: ExampleComponent, - template: - '', - }); - - this.render('{{example-component}}'); - - runTask(() => { - this.$('button').click(); - }); - - this.assert.equal(actionCount, 1, 'Click action only fired once.'); - this.assert.ok( - this.$('button').hasClass('selected'), - "Element with action handler has properly updated it's conditional class" - ); - - runTask(() => { - this.$('button').click(); - }); - - this.assert.equal(actionCount, 2, 'Second click action only fired once.'); - this.assert.ok( - !this.$('button').hasClass('selected'), - "Element with action handler has properly updated it's conditional class" - ); - } - } -); diff --git a/packages/@ember/-internals/glimmer/tests/integration/helpers/fn-test.js b/packages/@ember/-internals/glimmer/tests/integration/helpers/fn-test.js index a2aa0b74eda..759b3ebb80f 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/helpers/fn-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/helpers/fn-test.js @@ -135,7 +135,7 @@ moduleFor( } '@test can use `this` if bound prior to passing to fn'(assert) { - this.render(`{{stash stashedFn=(fn (action this.myFunc) this.arg1)}}`, { + this.render(`{{stash stashedFn=(fn (fn this.myFunc) this.arg1)}}`, { myFunc(arg1) { return `arg1: ${arg1}, arg2: ${this.arg2}`; }, diff --git a/packages/@ember/-internals/glimmer/tests/integration/helpers/readonly-test.js b/packages/@ember/-internals/glimmer/tests/integration/helpers/readonly-test.js index aa6415d37cc..f0c77893cdc 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/helpers/readonly-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/helpers/readonly-test.js @@ -36,50 +36,6 @@ moduleFor( // No U-R } - '@test passing an action to {{readonly}} avoids mutable cell wrapping'(assert) { - assert.expect(4); - let outer, inner; - - this.registerComponent('x-inner', { - ComponentClass: Component.extend({ - init() { - this._super(...arguments); - inner = this; - }, - }), - }); - - this.registerComponent('x-outer', { - ComponentClass: Component.extend({ - init() { - this._super(...arguments); - outer = this; - }, - }), - template: '{{x-inner onClick=(readonly this.onClick)}}', - }); - - this.render('{{x-outer onClick=(action this.doIt)}}', { - doIt() { - assert.ok(true, 'action was called'); - }, - }); - - assert.equal( - typeof outer.attrs.onClick, - 'function', - 'function itself is present in outer component attrs' - ); - outer.attrs.onClick(); - - assert.equal( - typeof inner.attrs.onClick, - 'function', - 'function itself is present in inner component attrs' - ); - inner.attrs.onClick(); - } - '@test updating a {{readonly}} property from above works'(assert) { let component; diff --git a/packages/@ember/-internals/glimmer/tests/integration/helpers/tracked-test.js b/packages/@ember/-internals/glimmer/tests/integration/helpers/tracked-test.js index 8fe282cbbbc..ba59a4dbbde 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/helpers/tracked-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/helpers/tracked-test.js @@ -28,7 +28,7 @@ moduleFor( this.registerComponent('person', { ComponentClass: PersonComponent, template: strip` - `, @@ -113,7 +113,7 @@ moduleFor( this.registerComponent('person', { ComponentClass: PersonComponent, template: strip` - `, @@ -155,7 +155,7 @@ moduleFor( this.registerComponent('num-list', { ComponentClass: NumListComponent, template: strip` - `, @@ -210,7 +210,7 @@ moduleFor( this.registerComponent('num-list', { ComponentClass: NumListComponent, template: strip` - `, diff --git a/packages/@ember/-internals/glimmer/tests/integration/input-test.js b/packages/@ember/-internals/glimmer/tests/integration/input-test.js index 731a01763b8..c0efd27c47f 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/input-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/input-test.js @@ -201,30 +201,16 @@ moduleFor( this.assertValue('hola', 'Value is used'); } - ['@test GH18211 input checked attribute, without a value, works with the action helper']() { - this.render(``, { - actions: { someAction() {} }, - }); - this.assertPropertyHasValue('checked', true); - } - - ['@test GH18211 input checked attribute, with a value, works with the action helper']() { - this.render(``, { - actions: { someAction() {} }, - }); - this.assertPropertyHasValue('checked', true); - } - ['@test GH18211 input checked attribute, without a value, works with attributes with values']() { - this.render(``, { - actions: { someAction() {} }, + this.render(``, { + someAction() {}, }); this.assertPropertyHasValue('checked', true); } ['@test GH18211 input checked attribute, without a value, works with event attributes']() { - this.render(``, { - actions: { someAction() {} }, + this.render(``, { + someAction() {}, }); this.assertPropertyHasValue('checked', true); } diff --git a/packages/@ember/-internals/views/index.ts b/packages/@ember/-internals/views/index.ts index b103f9f0550..bd82ffc6897 100644 --- a/packages/@ember/-internals/views/index.ts +++ b/packages/@ember/-internals/views/index.ts @@ -24,4 +24,3 @@ export { default as ViewStateSupport } from './lib/mixins/view_state_support'; export { default as ViewMixin } from './lib/mixins/view_support'; export { default as ActionSupport } from './lib/mixins/action_support'; export { MUTABLE_CELL } from './lib/compat/attrs'; -export { default as ActionManager } from './lib/system/action_manager'; diff --git a/packages/@ember/-internals/views/lib/system/action_manager.ts b/packages/@ember/-internals/views/lib/system/action_manager.ts deleted file mode 100644 index 430701cbcb0..00000000000 --- a/packages/@ember/-internals/views/lib/system/action_manager.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { ActionState } from '@ember/-internals/glimmer/lib/modifiers/action'; - -/** -@module ember -*/ - -export default function ActionManager() {} - -/** - Global action id hash. - - @private - @property registeredActions - @type Object -*/ -ActionManager.registeredActions = {} as Record; diff --git a/packages/@ember/-internals/views/lib/system/event_dispatcher.ts b/packages/@ember/-internals/views/lib/system/event_dispatcher.ts index 1e281c9bcaf..4d328839d1e 100644 --- a/packages/@ember/-internals/views/lib/system/event_dispatcher.ts +++ b/packages/@ember/-internals/views/lib/system/event_dispatcher.ts @@ -2,11 +2,7 @@ import { getOwner } from '@ember/-internals/owner'; import { assert } from '@ember/debug'; import { get, set } from '@ember/-internals/metal'; import EmberObject from '@ember/object'; -import { getElementView } from '@ember/-internals/views'; -import ActionManager from './action_manager'; import type { BootEnvironment } from '@ember/-internals/glimmer/lib/views/outlet'; -import type Component from '@ember/component'; -import type { ActionState } from '@ember/-internals/glimmer/lib/modifiers/action'; /** @module ember @@ -253,66 +249,6 @@ export default class EventDispatcher extends EmberObject { return; // nothing to do } - let viewHandler = (target: Element, event: Event) => { - let view = getElementView(target); - let result = true; - - if (view) { - // SAFETY: As currently written, this is not safe. Though it seems to always be true. - result = (view as Component).handleEvent(eventName, event); - } - - return result; - }; - - let actionHandler = (target: Element, event: Event) => { - let actionId = target.getAttribute('data-ember-action'); - let actions: Array | undefined; - - // In Glimmer2 this attribute is set to an empty string and an additional - // attribute it set for each action on a given element. In this case, the - // attributes need to be read so that a proper set of action handlers can - // be coalesced. - if (actionId === '') { - actions = []; - - for (let attr of target.attributes) { - let attrName = attr.name; - - if (attrName.indexOf('data-ember-action-') === 0) { - let action = ActionManager.registeredActions[attr.value]; - assert('[BUG] Missing action', action); - actions.push(action); - } - } - } else if (actionId) { - // FIXME: This branch is never called in tests. Improve tests or remove - let actionState = ActionManager.registeredActions[actionId]; - if (actionState) { - actions = [actionState]; - } - } - - // We have to check for actions here since in some cases, jQuery will trigger - // an event on `removeChild` (i.e. focusout) after we've already torn down the - // action handlers for the view. - if (!actions) { - // FIXME: This branch is never called in tests. Improve tests or remove - return; - } - - let result = true; - for (let index = 0; index < actions.length; index++) { - let action = actions[index]; - - if (action && action.eventName === eventName) { - // return false if any of the action handlers returns false - result = action.handler(event) && result; - } - } - return result; - }; - let handleEvent = (this._eventHandlers[event] = (event: Event) => { let target = event.target; @@ -320,27 +256,6 @@ export default class EventDispatcher extends EmberObject { `[BUG] Received event without an Element target: ${event.type}, ${target}`, target instanceof Element ); - - do { - if (getElementView(target)) { - if (viewHandler(target, event) === false) { - event.preventDefault(); - event.stopPropagation(); - break; - } else if (event.cancelBubble === true) { - break; - } - } else if ( - typeof target.hasAttribute === 'function' && - target.hasAttribute('data-ember-action') - ) { - if (actionHandler(target, event) === false) { - break; - } - } - - target = target.parentNode; - } while (target instanceof Element); }); rootElement.addEventListener(event, handleEvent); diff --git a/packages/@ember/application/tests/visit_test.js b/packages/@ember/application/tests/visit_test.js index 7b0887c28a7..411df556794 100644 --- a/packages/@ember/application/tests/visit_test.js +++ b/packages/@ember/application/tests/visit_test.js @@ -703,7 +703,7 @@ moduleFor( 'components/x-bar', `

X-Bar

- + ` ); @@ -712,10 +712,8 @@ moduleFor( Component.extend({ counter: service('sharedCounter'), - actions: { - incrementCounter() { - this.get('counter').increment(); - }, + incrementCounter() { + this.get('counter').increment(); }, init() { diff --git a/packages/@ember/object/tests/action_test.js b/packages/@ember/object/tests/action_test.js index 8acc0dca4de..806893c06f0 100644 --- a/packages/@ember/object/tests/action_test.js +++ b/packages/@ember/object/tests/action_test.js @@ -15,7 +15,7 @@ moduleFor( this.registerComponent('foo-bar', { ComponentClass: FooComponent, - template: "", + template: "", }); this.render('{{foo-bar}}'); @@ -52,10 +52,8 @@ moduleFor( assert.expect(4); let FooComponent = Component.extend({ - actions: { - foo() { - assert.ok(true, 'foo called!'); - }, + foo() { + assert.ok(true, 'foo called!'); }, }); @@ -67,10 +65,8 @@ moduleFor( } let BazComponent = BarComponent.extend({ - actions: { - baz() { - assert.ok(true, 'baz called!'); - }, + baz() { + assert.ok(true, 'baz called!'); }, }); @@ -84,10 +80,10 @@ moduleFor( this.registerComponent('qux-component', { ComponentClass: QuxComponent, template: strip` - - - - + + + + `, }); @@ -112,7 +108,7 @@ moduleFor( this.registerComponent('bar-bar', { ComponentClass: BarComponent, - template: "", + template: "", }); this.render('{{bar-bar}}'); @@ -136,7 +132,7 @@ moduleFor( this.registerComponent('bar-bar', { ComponentClass: BarComponent, - template: "", + template: "", }); this.render('{{bar-bar}}'); @@ -162,7 +158,7 @@ moduleFor( this.registerComponent('bar-bar', { ComponentClass: BarComponent, - template: "", + template: "", }); this.render('{{bar-bar}}'); @@ -247,7 +243,7 @@ moduleFor( this.registerComponent('foo-bar', { ComponentClass: FooComponent, - template: "", + template: "", }); this.render('{{foo-bar}}'); diff --git a/packages/ember/tests/component_context_test.js b/packages/ember/tests/component_context_test.js index fa88436a09e..cfa87f5bc10 100644 --- a/packages/ember/tests/component_context_test.js +++ b/packages/ember/tests/component_context_test.js @@ -191,7 +191,7 @@ moduleFor( `
{{#my-component}} - Fizzbuzz + Fizzbuzz {{/my-component}}
` @@ -200,10 +200,8 @@ moduleFor( this.add( 'controller:application', Controller.extend({ - actions: { - fizzbuzz() { - assert.ok(true, 'action triggered on parent'); - }, + fizzbuzz() { + assert.ok(true, 'action triggered on parent'); }, }) ); @@ -229,22 +227,18 @@ moduleFor( this.add( 'controller:application', Controller.extend({ - actions: { - fizzbuzz() { - assert.ok(false, 'action on the wrong context'); - }, + fizzbuzz() { + assert.ok(false, 'action on the wrong context'); }, }) ); this.addComponent('my-component', { ComponentClass: Component.extend({ - actions: { - fizzbuzz() { - assert.ok(true, 'action triggered on component'); - }, + fizzbuzz() { + assert.ok(true, 'action triggered on component'); }, }), - template: `Fizzbuzz`, + template: `Fizzbuzz`, }); return this.visit('/').then(() => { diff --git a/packages/ember/tests/controller_test.js b/packages/ember/tests/controller_test.js deleted file mode 100644 index 26c8a020a94..00000000000 --- a/packages/ember/tests/controller_test.js +++ /dev/null @@ -1,44 +0,0 @@ -import Controller from '@ember/controller'; -import { moduleFor, ApplicationTestCase, runTask } from 'internal-test-helpers'; -import { Component } from '@ember/-internals/glimmer'; - -/* - In Ember 1.x, controllers subtly affect things like template scope - and action targets in exciting and often inscrutable ways. This test - file contains integration tests that verify the correct behavior of - the many parts of the system that change and rely upon controller scope, - from the runtime up to the templating layer. -*/ - -moduleFor( - 'Template scoping examples', - class extends ApplicationTestCase { - ['@test Actions inside an outlet go to the associated controller'](assert) { - this.add( - 'controller:index', - Controller.extend({ - actions: { - componentAction() { - assert.ok(true, 'controller received the action'); - }, - }, - }) - ); - - this.addComponent('component-with-action', { - ComponentClass: Component.extend({ - classNames: ['component-with-action'], - click() { - this.action(); - }, - }), - }); - - this.addTemplate('index', '{{component-with-action action=(action "componentAction")}}'); - - return this.visit('/').then(() => { - runTask(() => this.$('.component-with-action').click()); - }); - } - } -); diff --git a/packages/ember/tests/integration/multiple-app-test.js b/packages/ember/tests/integration/multiple-app-test.js index b151999936a..34c9440eb01 100644 --- a/packages/ember/tests/integration/multiple-app-test.js +++ b/packages/ember/tests/integration/multiple-app-test.js @@ -48,11 +48,9 @@ moduleFor( resolver.add( 'component:special-button', Component.extend({ - actions: { - doStuff() { - let rootElement = getOwner(this).application.rootElement; - actions.push(rootElement); - }, + doStuff() { + let rootElement = getOwner(this).application.rootElement; + actions.push(rootElement); }, }) ); @@ -72,7 +70,7 @@ moduleFor( 'template:components/special-button', this.compile( ` - + `, { moduleName: 'my-app/templates/components/special-button.hbs', diff --git a/packages/ember/tests/routing/decoupled_basic_test.js b/packages/ember/tests/routing/decoupled_basic_test.js index 69b07d43d27..cd5a6783fc5 100644 --- a/packages/ember/tests/routing/decoupled_basic_test.js +++ b/packages/ember/tests/routing/decoupled_basic_test.js @@ -17,7 +17,6 @@ import { } from 'internal-test-helpers'; import { run } from '@ember/runloop'; import { addObserver } from '@ember/-internals/metal'; -import Mixin from '@ember/object/mixin'; import { service } from '@ember/service'; import Engine from '@ember/engine'; import { InternalTransition as Transition } from 'router_js'; @@ -321,253 +320,24 @@ moduleFor( return model; }, - actions: { - showStuff() { - stateIsNotCalled = false; - }, - }, - }) - ); - - this.addTemplate('home', '{{this.name}}'); - this.add( - 'controller:home', - Controller.extend({ - actions: { - showStuff(context) { - assert.ok(stateIsNotCalled, 'an event on the state is not triggered'); - assert.deepEqual(context, { name: 'Tom Dale' }, 'an event with context is passed'); - done(); - }, - }, - }) - ); - - await this.visit('/'); - - document.getElementById('qunit-fixture').querySelector('a').click(); - } - - async ['@test Events are triggered on the current state when defined in `actions` object']( - assert - ) { - let done = assert.async(); - - this.router.map(function () { - this.route('home', { path: '/' }); - }); - - let model = { name: 'Tom Dale' }; - let HomeRoute = Route.extend({ - model() { - return model; - }, - - actions: { - showStuff(obj) { - assert.ok(this instanceof HomeRoute, 'the handler is an App.HomeRoute'); - assert.deepEqual( - Object.assign({}, obj), - { name: 'Tom Dale' }, - 'the context is correct' - ); - done(); - }, - }, - }); - - this.add('route:home', HomeRoute); - this.addTemplate('home', '{{@model.name}}'); - - await this.visit('/'); - - document.getElementById('qunit-fixture').querySelector('a').click(); - } - - async ['@test Events defined in `actions` object are triggered on the current state when routes are nested']( - assert - ) { - let done = assert.async(); - - this.router.map(function () { - this.route('root', { path: '/' }, function () { - this.route('index', { path: '/' }); - }); - }); - - let model = { name: 'Tom Dale' }; - - let RootRoute = Route.extend({ - actions: { - showStuff(obj) { - assert.ok(this instanceof RootRoute, 'the handler is an App.HomeRoute'); - assert.deepEqual( - Object.assign({}, obj), - { name: 'Tom Dale' }, - 'the context is correct' - ); - done(); - }, - }, - }); - this.add('route:root', RootRoute); - this.add( - 'route:root.index', - Route.extend({ - model() { - return model; - }, - }) - ); - - this.addTemplate('root.index', '{{@model.name}}'); - - await this.visit('/'); - - document.getElementById('qunit-fixture').querySelector('a').click(); - } - - ['@test Events can be handled by inherited event handlers'](assert) { - assert.expect(4); - - let SuperRoute = Route.extend({ - actions: { - foo() { - assert.ok(true, 'foo'); - }, - bar(msg) { - assert.equal(msg, 'HELLO', 'bar hander in super route'); - }, - }, - }); - - let RouteMixin = Mixin.create({ - actions: { - bar(msg) { - assert.equal(msg, 'HELLO', 'bar handler in mixin'); - this._super(msg); - }, - }, - }); - - this.add( - 'route:home', - SuperRoute.extend(RouteMixin, { - actions: { - baz() { - assert.ok(true, 'baz', 'baz hander in route'); - }, - }, - }) - ); - this.addTemplate( - 'home', - ` - Do foo - Do bar with arg - Do bar - ` - ); - - return this.visit('/').then(() => { - let rootElement = document.getElementById('qunit-fixture'); - rootElement.querySelector('.do-foo').click(); - rootElement.querySelector('.do-bar-with-arg').click(); - rootElement.querySelector('.do-baz').click(); - }); - } - - async ['@test Actions are not triggered on the controller if a matching action name is implemented as a method']( - assert - ) { - let done = assert.async(); - - this.router.map(function () { - this.route('home', { path: '/' }); - }); - - let model = { name: 'Tom Dale' }; - let stateIsNotCalled = true; - - this.add( - 'route:home', - Route.extend({ - model() { - return model; - }, - - actions: { - showStuff(context) { - assert.ok(stateIsNotCalled, 'an event on the state is not triggered'); - assert.deepEqual(context, { name: 'Tom Dale' }, 'an event with context is passed'); - done(); - }, + showStuff() { + stateIsNotCalled = false; }, }) ); - this.addTemplate('home', '{{this.name}}'); - + this.addTemplate('home', '{{this.name}}'); this.add( 'controller:home', Controller.extend({ - showStuff() { - stateIsNotCalled = false; + showStuff(context) { assert.ok(stateIsNotCalled, 'an event on the state is not triggered'); - }, - }) - ); - - await this.visit('/'); - - document.getElementById('qunit-fixture').querySelector('a').click(); - } - - async ['@test actions can be triggered with multiple arguments'](assert) { - let done = assert.async(); - this.router.map(function () { - this.route('root', { path: '/' }, function () { - this.route('index', { path: '/' }); - }); - }); - - let model1 = { name: 'Tilde' }; - let model2 = { name: 'Tom Dale' }; - - let RootRoute = Route.extend({ - actions: { - showStuff(obj1, obj2) { - assert.ok(this instanceof RootRoute, 'the handler is an App.HomeRoute'); - assert.deepEqual( - Object.assign({}, obj1), - { name: 'Tilde' }, - 'the first context is correct' - ); - assert.deepEqual( - Object.assign({}, obj2), - { name: 'Tom Dale' }, - 'the second context is correct' - ); + assert.deepEqual(context, { name: 'Tom Dale' }, 'an event with context is passed'); done(); }, - }, - }); - - this.add('route:root', RootRoute); - - this.add( - 'controller:root.index', - Controller.extend({ - model1: model1, - model2: model2, }) ); - this.addTemplate( - 'root.index', - '{{this.model1.name}}' - ); - await this.visit('/'); document.getElementById('qunit-fixture').querySelector('a').click(); @@ -1221,57 +991,6 @@ moduleFor( return this.visit('/nork').then(() => this.visit('/dork')); } - ['@test Actions can be handled by inherited action handlers'](assert) { - assert.expect(4); - - let SuperRoute = Route.extend({ - actions: { - foo() { - assert.ok(true, 'foo'); - }, - bar(msg) { - assert.equal(msg, 'HELLO'); - }, - }, - }); - - let RouteMixin = Mixin.create({ - actions: { - bar(msg) { - assert.equal(msg, 'HELLO'); - this._super(msg); - }, - }, - }); - - this.add( - 'route:home', - SuperRoute.extend(RouteMixin, { - actions: { - baz() { - assert.ok(true, 'baz'); - }, - }, - }) - ); - - this.addTemplate( - 'home', - ` - Do foo - Do bar with arg - Do bar - ` - ); - - return this.visit('/').then(() => { - let rootElement = document.getElementById('qunit-fixture'); - rootElement.querySelector('.do-foo').click(); - rootElement.querySelector('.do-bar-with-arg').click(); - rootElement.querySelector('.do-baz').click(); - }); - } - ['@test transitionTo returns Transition when passed a route name'](assert) { assert.expect(1); diff --git a/packages/ember/tests/routing/query_params_test.js b/packages/ember/tests/routing/query_params_test.js index 4f820f86996..dc4ced410ab 100644 --- a/packages/ember/tests/routing/query_params_test.js +++ b/packages/ember/tests/routing/query_params_test.js @@ -745,15 +745,13 @@ moduleFor( ) { this.addTemplate( 'application', - '{{this.foo}}{{outlet}}' + '{{this.foo}}{{outlet}}' ); this.setSingleQPController('application', 'foo', 1, { - actions: { - increment() { - this.incrementProperty('foo'); - this.send('refreshRoute'); - }, + increment() { + this.incrementProperty('foo'); + this.send('refreshRoute'); }, });