Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapters #511

Merged
merged 9 commits into from
Jan 6, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 19 additions & 129 deletions addon-test-support/-private/action.js
Original file line number Diff line number Diff line change
@@ -1,147 +1,37 @@
import { resolve } from 'rsvp';
import { getExecutionContext } from './execution_context';
import { getRoot, assign, buildSelector } from './helpers';
import Ceibo from 'ceibo';
import { throwBetterError } from './better-errors';
import { assign } from './helpers';
import { run } from './run';

export default function action(query, cb) {
return {
isDescriptor: true,

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

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

return run(this, query, (context) => {
const selector = buildSelector(this, query.selector, query);

let res;
try {
res = cb.bind(context)(...args);
} catch(e) {
throwBetterError(this, query.key, e, { selector });
}

return resolve(res).catch((e) => {
throwBetterError(this, query.key, e, { selector });
});
})
return run(this, query, (executionContext) => {
return cb.bind(executionContext)(...args)
});
}
}
}
}

/**
* Run action
*
* @param {Ceibo} node Page object node to run action on
* @param {object} query
* @param {Function} cb Some async activity callback
* @returns {Ceibo}
*/
function run(node, query, cb) {
const adapter = getExecutionContext(node);
const executionContext = Object.freeze({
query,
node,
adapter
});

const chainedRoot = getRoot(node)._chainedTree;

if (typeof adapter.andThen === 'function') {
// With old ember-testing helpers, we don't make the difference between
// chanined VS independent action invocations. Awaiting for the previous
// action settlement, before invoke a new action, is a part of
// the legacy testing helpers adapters for backward compat reasons
chainedRoot._promise = adapter.andThen.bind(executionContext)(cb);

return node;
} else if (!chainedRoot) {
// 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.
let root = getRoot(node)
root._promise = resolve(root._promise).then(() => cb(executionContext));
function normalizeArgs(key, query, cb, args) {
let formattedKey = `${key}(${args.length
? `"${args.map((a) => String(a)).join('", "')}"`
: ``})`;

return node;
if (typeof query === 'function') {
cb = query;
query = {
key: formattedKey
};
} else {
// Store our invocation result on the chained root
// so that chained calls can find it to wait on it.
chainedRoot._promise = cb(executionContext);

return chainable(node);
}
}

export function chainable(branch) {
if (isChainedNode(branch)) {
return branch;
}

// See explanation in `create.js` -- here instead of returning the node on
// which our method was invoked, we find and return our node's mirror in the
// chained tree so calls to it can be recognized as chained calls, and
// trigger the chained-call waiting behavior.

// Collecting node keys to build a path to our node, and then use that
// to walk back down the chained tree to our mirror node.
let path = [];
let node;

for (node = branch; node; node = Ceibo.parent(node)) {
path.unshift(Ceibo.meta(node).key);
query = assign({}, query, {
key: formattedKey
});
}
// The path will end with the root's key, 'root', so shift that back off
path.shift();

node = getRoot(branch)._chainedTree;
path.forEach((key) => {
node = getChildNode(node, key)
});

return node;
}

function isChainedNode(node) {
let root = getRoot(node);

return !root._chainedTree;
}

function getChildNode(node, key) {
// Normally an item's key is just its property name, but collection
// items' keys also include their index. Collection item keys look like
// `foo[2]` and legacy collection item keys look like `foo(2)`.
let match;
if ((match = /\[(\d+)\]$/.exec(key))) {
// This is a collection item
let [ indexStr, index ] = match;
let name = key.slice(0, -indexStr.length);

return node[name].objectAt(parseInt(index, 10));
} else if ((match = /\((\d+)\)$/.exec(key))) {
// This is a legacy collection item
let [ indexStr, index ] = match;
let name = key.slice(0, -indexStr.length);

return node[name](parseInt(index, 10));
} else {
return node[key];
}
return { query, cb };
}
9 changes: 9 additions & 0 deletions addon-test-support/-private/better-errors.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import Ceibo from 'ceibo';
import { buildSelector } from './helpers';

export const ELEMENT_NOT_FOUND = 'Element not found.';

export function throwContextualError(context, e) {
const { query, node } = context;

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

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

/**
* Throws an error with a descriptive message.
*
Expand Down
57 changes: 57 additions & 0 deletions addon-test-support/-private/chainable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Ceibo from 'ceibo';
import { getRoot } from './helpers';

export function isChainedNode(node) {
let root = getRoot(node);

return !root._chainedTree;
}

export function chainable(branch) {
if (isChainedNode(branch)) {
return branch;
}

// See explanation in `create.js` -- here instead of returning the node on
// which our method was invoked, we find and return our node's mirror in the
// chained tree so calls to it can be recognized as chained calls, and
// trigger the chained-call waiting behavior.
// Collecting node keys to build a path to our node, and then use that
// to walk back down the chained tree to our mirror node.
let path = [];
let node;
for (node = branch; node; node = Ceibo.parent(node)) {
path.unshift(Ceibo.meta(node).key);
}

// The path will end with the root's key, 'root', so shift that back off
path.shift();
node = getRoot(branch)._chainedTree;
path.forEach((key) => {
node = getChildNode(node, key);
});

return node;
}

function getChildNode(node, key) {
// Normally an item's key is just its property name, but collection
// items' keys also include their index. Collection item keys look like
// `foo[2]` and legacy collection item keys look like `foo(2)`.
let match;
if ((match = /\[(\d+)\]$/.exec(key))) {
// This is a collection item
let [ indexStr, index ] = match;
let name = key.slice(0, -indexStr.length);

return node[name].objectAt(parseInt(index, 10));
} else if ((match = /\((\d+)\)$/.exec(key))) {
// This is a legacy collection item
let [ indexStr, index ] = match;
let name = key.slice(0, -indexStr.length);

return node[name](parseInt(index, 10));
} else {
return node[key];
}
}
93 changes: 0 additions & 93 deletions addon-test-support/-private/compatibility.js

This file was deleted.

Loading