import { RenderingTest, moduleFor } from '../../utils/test-case';
import { strip } from '../../utils/abstract-test-case';
import { Component } from '../../utils/helpers';
import { set } from '@ember/-internals/metal';
import {
subscribe as instrumentationSubscribe,
reset as instrumentationReset,
} from '@ember/instrumentation';
import { EMBER_IMPROVED_INSTRUMENTATION } from '@ember/canary-features';
import { Object as EmberObject, A as emberA } from '@ember/-internals/runtime';
import { ActionManager } from '@ember/-internals/views';
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)
);
}
const isIE11 = !window.ActiveXObject && 'ActiveXObject' in window;
if (EMBER_IMPROVED_INSTRUMENTATION) {
moduleFor(
'Helpers test: element action instrumentation',
class extends RenderingTest {
teardown() {
super.teardown();
instrumentationReset();
}
['@test action should fire interaction event with proper params']() {
let subscriberCallCount = 0;
let subscriberPayload = null;
let ExampleComponent = Component.extend({
actions: {
foo() {},
},
});
this.registerComponent('example-component', {
ComponentClass: ExampleComponent,
template: '',
});
instrumentationSubscribe('interaction.ember-action', {
before() {
subscriberCallCount++;
},
after(name, time, payload) {
subscriberPayload = payload;
},
});
this.render('{{example-component}}');
this.assert.equal(subscriberCallCount, 0, 'subscriber has not been called');
this.runTask(() => this.rerender());
this.assert.equal(subscriberCallCount, 0, 'subscriber has not been called');
this.runTask(() => {
this.$('button').click();
});
this.assert.equal(subscriberCallCount, 1, 'subscriber has been called 1 time');
this.assert.equal(subscriberPayload.name, 'foo', 'subscriber called with correct name');
this.assert.equal(subscriberPayload.args[0], 'bar', 'subscriber called with correct args');
}
}
);
}
moduleFor(
'Helpers test: element action',
class extends RenderingTest {
['@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');
this.runTask(() => this.rerender());
this.assert.equal(fooCallCount, 0, 'foo has not been called');
this.runTask(() => {
this.$('button').click();
});
this.assert.equal(fooCallCount, 1, 'foo has been called 1 time');
this.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');
this.runTask(() => this.rerender());
this.assert.deepEqual(fooArgs, [], 'foo has not been called');
this.runTask(() => {
this.$('button').click();
});
this.assert.deepEqual(fooArgs, ['a'], 'foo has not been called');
this.runTask(() => {
component.set('member', 'b');
});
this.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}}');
this.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}}');
this.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}}
`);
this.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}}');
this.runTask(() => {
this.$('a').click();
});
this.assert.equal(firstEdit, 1);
this.runTask(() => {
set(component, 'theTarget', second);
});
this.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}}');
this.runTask(() => {
this.$('a[data-ember-action]').trigger('click', { altKey: true });
});
this.assert.equal(editHandlerWasCalled, true, 'the event handler was called');
this.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}}');
this.runTask(() => {
this.$('a[data-ember-action]').trigger('click', { altKey: true });
});
this.assert.equal(editHandlerWasCalled, true, 'the event handler was called');
this.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}}');
this.runTask(() => {
this.$('a[data-ember-action]').trigger('click', { altKey: true });
});
this.assert.equal(editHandlerWasCalled, true, 'the event handler was called');
editHandlerWasCalled = false;
this.runTask(() => {
component.set('acceptedKeys', '');
});
this.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}}');
this.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;
this.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;
this.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}}');
this.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;
this.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;
this.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}}');
this.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;
this.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;
this.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}}');
this.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;
this.runTask(() => {
component.set('shouldBubble', true);
});
this.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 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 items as |item|}}click me{{/each}}',
});
this.render('{{example-component}}');
this.runTask(() => {
this.$('a').click();
});
this.assert.equal(editHandlerWasCalled, true, 'the event handler was called');
}
['@test it should work properly in a {{#with 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:
'{{#with something as |somethingElse|}}click me{{/with}}',
});
this.render('{{example-component}}');
this.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 isActive}}click me{{/if}}',
});
this.render('{{example-component isActive=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');
this.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');
this.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');
this.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}}');
this.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}}');
this.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}}');
this.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}}');
this.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}}');
this.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}}');
this.runTask(() => {
this.$('a').click();
});
this.assert.ok(editHandlerWasCalled, 'the event handler was called on click');
editHandlerWasCalled = false;
this.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}}');
this.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}}');
this.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}`);
// IE11 does not allow simulated events to have a valid `defaultPrevented`
if (!isIE11) {
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}}');
this.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 => {
test.runTask(() => {
component.set('hookMeUp', propertyValue);
});
test.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 allactions as |allaction|}}{{allaction.title}}{{/each}}',
});
this.render('{{example-component}}');
let test = this;
let testBoundAction = propertyValue => {
test.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}}');
this.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, `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}}');
this.runTask(() => {
this.$('a').trigger('click');
});
this.assert.ok(clickActionWasCalled, 'the clicked action was called');
this.runTask(() => {
this.$('a').trigger('dblclick');
});
this.assert.ok(doubleClickActionWasCalled, 'the doubleClicked 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;
this.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;
this.runTask(() => {
event = this.$('a').trigger(event)[0];
});
this.assert.equal(event.defaultPrevented, false, 'should not preventDefault');
this.runTask(() => {
component.set('shouldPreventDefault', true);
event = this.$('a').trigger('click')[0];
});
// IE11 does not allow simulated events to have a valid `defaultPrevented`
if (!isIE11) {
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;
expectDeprecation(() => {
this.sendAction();
}, /You called (.*).sendAction\((.*)\) but Component#sendAction is deprecated. Please use closure actions instead./);
},
});
this.registerComponent('outer-component', {
ComponentClass: OuterComponent,
template: strip`
{{#middle-component}}
{{inner-component 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}}');
this.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();
this.runTask(() => {
this.$('button').click();
});
this.assertText('Clicked!');
this.runTask(() => {
component.set('label', 'Dun clicked');
});
this.assertText('Dun clicked');
this.runTask(() => {
this.$('button').click();
});
this.assertText('Clicked!');
this.runTask(() => {
component.set('label', undefined);
});
this.assertText('Click me');
}
['@test it supports non-registered actions [GH#14888]']() {
this.render(
`
{{#if show}}
{{/if}}
`,
{ show: true }
);
this.assert.equal(
this.$('button')
.text()
.trim(),
'Show (true)'
);
// We need to focus in to simulate an actual click.
this.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}}');
this.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"
);
this.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"
);
}
}
);