Skip to content

Commit

Permalink
fix action to have proper this
Browse files Browse the repository at this point in the history
  • Loading branch information
ro0gr committed Nov 20, 2022
1 parent b1e38b3 commit f640594
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 122 deletions.
37 changes: 23 additions & 14 deletions addon/src/-private/action.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,43 @@
import { getter } from '../macros/index';
import { run } from './run';
import { throwContextualError } from './better-errors';

export default function action(query, cb) {
export default function action(options, cb) {
return getter(function (key) {
return function (...args) {
({ query, cb } = normalizeArgs(key, query, cb, args));
({ options, cb } = normalizeArgs(key, options, cb, args));

return run(this, query, (executionContext) => {
return cb.bind(executionContext)(...args);
return run(this, () => {
try {
const invocation = cb.bind(this)(...args);

return Promise.resolve(invocation).catch((e) => {
throwContextualError(this, options, e);
});
} catch (e) {
throwContextualError(this, options, e);
}
});
};
});
}

function normalizeArgs(key, query, cb, args) {
let formattedKey = `${key}(${
function normalizeArgs(key, options, cb, args) {
const formattedKey = `${key}(${
args.length ? `"${args.map((a) => String(a)).join('", "')}"` : ``
})`;

if (typeof query === 'function') {
cb = query;
query = {
key: formattedKey,
if (typeof options === 'function') {
cb = options;
options = {
pageObjectKey: formattedKey,
};
} else {
query = {
...query,
key: formattedKey,
options = {
...options,
pageObjectKey: formattedKey,
};
}

return { query, cb };
return { options, cb };
}
8 changes: 3 additions & 5 deletions addon/src/-private/better-errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ import { buildSelector } from './helpers';

export const ELEMENT_NOT_FOUND = 'Element not found.';

export function throwContextualError(context, e) {
const { query, node } = context;
export function throwContextualError(node, filters, e) {
const selector = buildSelector(node, filters.selector, filters);

const selector = buildSelector(node, query.selector, query);

throwBetterError(node, query.key, e, { selector });
throwBetterError(node, filters.pageObjectKey, e, { selector });
}

/**
Expand Down
33 changes: 4 additions & 29 deletions addon/src/-private/run.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { getAdapter } from '../adapters/index';
import { resolve } from 'rsvp';
import { getRoot } from './helpers';
import { throwContextualError } from './better-errors';
import { chainable, isChainedNode } from './chainable';

/**
Expand All @@ -11,44 +10,20 @@ import { chainable, isChainedNode } from './chainable';
* @param {Function} cb Some async activity callback
* @returns {Ceibo}
*/
export function run(node, query, cb) {
const adapter = getAdapter();

const executionContext = Object.freeze({
query,
node,
adapter,
});

export function run(node, cb) {
const root = getRoot(node);
if (isChainedNode(node)) {
// Our root is already the root of the chained tree,
// we need to wait on its promise if it has one so the
// previous invocations can resolve before we run ours.
root._promise = Promise.resolve(root._promise).then(() =>
invokeHelper(executionContext, cb)
);
root._promise = resolve(root._promise).then(() => cb());

return node;
} else {
// Store our invocation result on the chained root
// so that chained calls can find it to wait on it.
root._chainedTree._promise = invokeHelper(executionContext, cb);
root._chainedTree._promise = cb();

return chainable(node);
}
}

function invokeHelper(context, cb) {
let res;

try {
res = cb(context);
} catch (e) {
throwContextualError(context, e);
}

return Promise.resolve(res).catch((e) => {
throwContextualError(context, e);
});
}
5 changes: 3 additions & 2 deletions addon/src/properties/blurrable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import action from '../-private/action';
import { findOne } from '../-private/finders';
import { getAdapter } from '../adapters/index';

/**
*
Expand Down Expand Up @@ -69,9 +70,9 @@ export function blurrable(selector = '', userOptions = {}) {
selector,
},
function () {
const element = findOne(this.node, this.query.selector, this.query);
const element = findOne(this, selector, userOptions);

return this.adapter.blur(element);
return getAdapter().blur(element);
}
);
}
33 changes: 14 additions & 19 deletions addon/src/properties/click-on-text.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import action from '../-private/action';
import { findOne, findMany } from '../-private/finders';
import { getAdapter } from '../adapters/index';

/**
* Clicks on an element containing specified text.
Expand Down Expand Up @@ -83,28 +84,22 @@ import { findOne, findMany } from '../-private/finders';
* @param {string} options.testContainer - Context where to search elements in the DOM
* @return {Descriptor}
*/
export function clickOnText(scope, userOptions = {}) {
return action(
{
export function clickOnText(selector, userOptions = {}) {
return action({ ...userOptions, selector }, function (textToClick) {
const options = {
...userOptions,
selector: scope,
},
function (textToClick) {
this.query.contains = textToClick;
contains: textToClick,
// find the deepest node containing a text to click
this.query.last = true;
last: true,
};

const childSelector = `${scope || ''} `;
let selector;
if (findMany(this.node, childSelector, this.query).length) {
selector = childSelector;
} else {
selector = scope;
}
const childSelector = `${selector || ''} *`;
const byTextSelector = findMany(this, childSelector, options).length
? childSelector
: selector;

const element = findOne(this.node, selector, this.query);
const element = findOne(this, byTextSelector, options);

return this.adapter.click(element);
}
);
return getAdapter().click(element);
});
}
5 changes: 3 additions & 2 deletions addon/src/properties/clickable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import action from '../-private/action';
import { findOne } from '../-private/finders';
import { getAdapter } from '../adapters/index';

/**
* Clicks elements matched by a selector.
Expand Down Expand Up @@ -64,8 +65,8 @@ import { findOne } from '../-private/finders';
*/
export function clickable(selector, userOptions = {}) {
return action({ ...userOptions, selector }, function () {
const element = findOne(this.node, this.query.selector, this.query);
const element = findOne(this, selector, userOptions);

return this.adapter.click(element);
return getAdapter().click(element);
});
}
11 changes: 6 additions & 5 deletions addon/src/properties/fillable.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { findMany } from '../-private/finders';
import action from '../-private/action';
import { findOne } from '../-private/finders';
import { getAdapter } from '../adapters/index';

/**
* Alias for `fillable`, which works for inputs, HTML select menus, and
Expand Down Expand Up @@ -127,20 +128,20 @@ export function fillable(selector = '', userOptions = {}) {

let scopeSelector = selector;
if (clue) {
scopeSelector = findSelectorByClue(this, clue);
scopeSelector = findSelectorByClue(this, selector, userOptions, clue);
if (!scopeSelector) {
throw new Error(`Can not find element by clue: "${clue}".`);
}
}

const element = findOne(this.node, scopeSelector, this.query);
const element = findOne(this, scopeSelector, userOptions);

return this.adapter.fillIn(element, content);
return getAdapter().fillIn(element, content);
}
);
}

function findSelectorByClue({ node, query }, clue) {
function findSelectorByClue(node, selector, findOptions, clue) {
let cssClues = ['input', 'textarea', 'select', '[contenteditable]']
.map((tag) => [
`${tag}[data-test="${clue}"]`,
Expand All @@ -152,6 +153,6 @@ function findSelectorByClue({ node, query }, clue) {
.reduce((total, other) => total.concat(other), []);

return cssClues.find((extraScope) => {
return findMany(node, `${query.selector} ${extraScope}`, query)[0];
return findMany(node, `${selector} ${extraScope}`, findOptions)[0];
});
}
5 changes: 3 additions & 2 deletions addon/src/properties/focusable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import action from '../-private/action';
import { findOne } from '../-private/finders';
import { getAdapter } from '../adapters/index';

/**
*
Expand Down Expand Up @@ -66,8 +67,8 @@ export function focusable(selector = '', userOptions = {}) {
const query = { ...userOptions, selector };

return action(query, function () {
const element = findOne(this.node, this.query.selector, this.query);
const element = findOne(this, selector, query);

return this.adapter.focus(element);
return getAdapter().focus(element);
});
}
25 changes: 11 additions & 14 deletions addon/src/properties/triggerable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import action from '../-private/action';
import { findOne } from '../-private/finders';
import { getAdapter } from '../adapters/index';

/**
*
Expand Down Expand Up @@ -79,20 +80,16 @@ import { findOne } from '../-private/finders';
* @return {Descriptor}
*/
export function triggerable(event, selector, userOptions = {}) {
return action(
{
...userOptions,
selector,
},
function (eventProperties = {}) {
const mergedEventProperties = {
...userOptions.eventProperties,
...eventProperties,
};
const { eventProperties: initialEventProperties } = userOptions;

const element = findOne(this.node, this.query.selector, this.query);
return action({ ...userOptions, selector }, function (eventProperties = {}) {
const mergedEventProperties = {
...initialEventProperties,
...eventProperties,
};

return this.adapter.triggerEvent(element, event, mergedEventProperties);
}
);
const element = findOne(this, selector, userOptions);

return getAdapter().triggerEvent(element, event, mergedEventProperties);
});
}
3 changes: 2 additions & 1 deletion addon/src/properties/visitable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { $ } from '../-private/helpers';
import action from '../-private/action';
import { getAdapter } from '../adapters/index';

function fillInDynamicSegments(path, params) {
return path
Expand Down Expand Up @@ -97,6 +98,6 @@ export function visitable(path) {

fullPath = appendQueryParams(fullPath, params);

return this.adapter.visit(fullPath);
return getAdapter().visit(fullPath);
});
}
34 changes: 5 additions & 29 deletions test-app/tests/unit/-private/action-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ module('Unit | action', function (hooks) {
setAdapter(initialAdapter);
});

let invoked, finished, executionContext;
let invoked, finished, node;

const testable = (query) => {
return action(query, function (id, deferred) {
invoked.push(id);
executionContext = this;
node = this;

return deferred.promise.then(() => {
finished.push(id);
Expand All @@ -59,7 +59,7 @@ module('Unit | action', function (hooks) {
hooks.beforeEach(function () {
invoked = [];
finished = [];
executionContext = null;
node = null;
});

test('it works', async function (assert) {
Expand All @@ -72,16 +72,8 @@ module('Unit | action', function (hooks) {
const d1 = new Deferred();
p.run(1, d1);

assert.equal(
typeof executionContext === 'object' && executionContext !== null,
true
);
assert.deepEqual(executionContext.query, {
key: `run("1", "[object Object]")`,
selector: '.Selector',
});
assert.equal(executionContext.node, p, '');
assert.equal(executionContext.adapter instanceof DummyAdapter, true);
assert.true(typeof node === 'object' && node !== null);
assert.equal(node, p, '');

assert.deepEqual(invoked, [1]);
assert.deepEqual(finished, []);
Expand All @@ -92,22 +84,6 @@ module('Unit | action', function (hooks) {
assert.deepEqual(finished, [1]);
});

test('this is frozen', async function (assert) {
const p = create({
scope: 'it works',

run: action(function () {
executionContext = this;
}),
});

await p.run();

assert.throws(() => {
executionContext.test = 1;
});
});

test('it handles sync errors', async function (assert) {
const p = create({
scope: '.Scope',
Expand Down

0 comments on commit f640594

Please sign in to comment.