Skip to content

Commit

Permalink
query
Browse files Browse the repository at this point in the history
use Query directly in finders

in order to have get rid of build selector

- breaking: ignore trailing spaces in selectors
  • Loading branch information
ro0gr committed Sep 5, 2022
1 parent d5a168b commit 8eef08e
Show file tree
Hide file tree
Showing 15 changed files with 572 additions and 228 deletions.
47 changes: 19 additions & 28 deletions addon/src/-private/action.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,35 @@
import { getter } from '../macros/index';
import { buildSelector } from './build-selector';
import { Query } from './query';
import { run } from './run';

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

const node = this;
return run(
{
node,
filters: {
...options,
},
},
() => {
return cb.bind(this)(...args);
}
const { locator, cb, label } = normalizeArgs(
key,
options,
callback,
args
);

const query = new Query(this, locator);

return run(query, label, () => {
return cb.bind(this)(...args);
});
};
});
}

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

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

return { options, cb };
return { locator, cb, label };
}
11 changes: 4 additions & 7 deletions addon/src/-private/better-errors.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import Ceibo from '@ro0gr/ceibo';
import { buildSelector } from './build-selector';

export const ELEMENT_NOT_FOUND = 'Element not found.';

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

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

throwBetterError(node, filters.pageObjectKey, e, { selector });
export function throwQueryError(query, label, e) {
throwBetterError(query.node, label, e, {
selector: query.toString(),
});
}

/**
Expand Down
92 changes: 6 additions & 86 deletions addon/src/-private/build-selector.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Ceibo from '@ro0gr/ceibo';
import deprecate from './deprecate';
import { Query } from './query';

/**
* @public
Expand Down Expand Up @@ -46,89 +45,10 @@ import deprecate from './deprecate';
*/

export function buildSelector(node, targetSelector, options) {
return new Selector(node, options.scope, targetSelector, options).toString();
}

export class Selector {
constructor(node, scope, selector, filters) {
this.targetNode = node;
this.targetScope = scope || '';
this.targetSelector = selector || '';
this.targetFilters = filters;
}

toString() {
let scope;
let filters;

if (this.targetFilters.resetScope) {
scope = this.targetScope;
} else {
scope = this.calculateScope(this.targetNode, this.targetScope);
}

if (`${scope} ${this.targetSelector}`.indexOf(',') > -1) {
deprecate(
'comma-separated-selectors',
'Usage of comma separated selectors is deprecated in ember-cli-page-object',
'1.16.0',
'2.0.0'
);
}

filters = this.calculateFilters(this.targetFilters);

let selector = `${scope} ${this.targetSelector}${filters}`.trim();

if (!selector.length) {
// When an empty selector is resolved take the first direct child of the
// testing container.
selector = ':first';
}

return selector;
}

calculateFilters() {
let filters = [];

if (this.targetFilters.visible) {
filters.push(`:visible`);
}

if (this.targetFilters.contains) {
filters.push(`:contains("${this.targetFilters.contains}")`);
}

if (typeof this.targetFilters.at === 'number') {
filters.push(`:eq(${this.targetFilters.at})`);
} else if (this.targetFilters.last) {
filters.push(':last');
}

return filters.join('');
}

calculateScope(node, targetScope) {
let scopes = this.getScopes(node);

scopes.reverse();
scopes.push(targetScope);

return scopes.join(' ').trim();
}

getScopes(node) {
let scopes = [];

if (node.scope) {
scopes.push(node.scope);
}

if (!node.resetScope && Ceibo.parent(node)) {
scopes = scopes.concat(this.calculateScope(Ceibo.parent(node)));
}
const q = new Query(node, {
...options,
selector: targetSelector,
});

return scopes;
}
return q.toString();
}
8 changes: 8 additions & 0 deletions addon/src/-private/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,11 @@ export function isVisible(element) {
element.getClientRects().length
);
}

export function text(element) {
return element.textContent;
}

export function containsText(element, searchText) {
return text(element).indexOf(searchText) > -1;
}
73 changes: 33 additions & 40 deletions addon/src/-private/finders.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,24 @@
import { $, findClosestValue, guardMultiple } from './helpers';
import { getAdapter } from '../adapters/index';
import { buildSelector } from './build-selector';
import { throwBetterError, ELEMENT_NOT_FOUND } from './better-errors';

function getContainer(pageObjectNode, options) {
return (
options.testContainer ||
findClosestValue(pageObjectNode, 'testContainer') ||
getAdapter().testContainer
);
}
import { $, guardMultiple } from './helpers';
import { throwQueryError, ELEMENT_NOT_FOUND } from './better-errors';
import { Query } from './query';

/**
* Finds a single element, otherwise fails
*
* @private
*/
export function findOne(pageObjectNode, targetSelector, options = {}) {
const elements = findMany(pageObjectNode, targetSelector, options);
export function findOne(pageObjectNode, selector, options = {}) {
const query = new Query(pageObjectNode, {
...options,
selector,
});

const elements = query.all();

const selector = buildSelector(pageObjectNode, targetSelector, options);
guardMultiple(elements, selector);
guardMultiple(elements, query);

if (elements.length === 0) {
throwBetterError(pageObjectNode, options.pageObjectKey, ELEMENT_NOT_FOUND, {
selector,
});
throwQueryError(query, options.pageObjectKey, ELEMENT_NOT_FOUND);
}

return elements[0];
Expand All @@ -36,33 +29,31 @@ export function findOne(pageObjectNode, targetSelector, options = {}) {
*
* @private
*/
export function findMany(pageObjectNode, targetSelector, options = {}) {
const selector = buildSelector(pageObjectNode, targetSelector, options);
const container = getContainer(pageObjectNode, options);
export function findMany(pageObjectNode, selector, options = {}) {
const query = new Query(pageObjectNode, {
...options,
selector,
});

return $(selector, container).toArray();
return query.all();
}

/**
* @private
* @deprecated
*/
export function findElementWithAssert(
pageObjectNode,
targetSelector,
options = {}
) {
const selector = buildSelector(pageObjectNode, targetSelector, options);
const container = getContainer(pageObjectNode, options);
export function findElementWithAssert(pageObjectNode, selector, options = {}) {
const query = new Query(pageObjectNode, {
...options,
selector,
});

let $elements = $(selector, container);
let $elements = $(query.all());

guardMultiple($elements, selector, options.multiple);
guardMultiple($elements, query, options.multiple);

if ($elements.length === 0) {
throwBetterError(pageObjectNode, options.pageObjectKey, ELEMENT_NOT_FOUND, {
selector,
});
throwQueryError(query, options.pageObjectKey, ELEMENT_NOT_FOUND);
}

return $elements;
Expand All @@ -72,13 +63,15 @@ export function findElementWithAssert(
* @private
* @deprecated
*/
export function findElement(pageObjectNode, targetSelector, options = {}) {
const selector = buildSelector(pageObjectNode, targetSelector, options);
const container = getContainer(pageObjectNode, options);
export function findElement(pageObjectNode, selector, options = {}) {
const query = new Query(pageObjectNode, {
...options,
selector,
});

let $elements = $(selector, container);
let $elements = $(query.all());

guardMultiple($elements, selector, options.multiple);
guardMultiple($elements, query, options.multiple);

return $elements;
}
46 changes: 3 additions & 43 deletions addon/src/-private/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ if (macroCondition(dependencySatisfies('@ember/jquery', '*'))) {
}

export { jQuery as $ };

function isPresent(value) {
return typeof value !== 'undefined';
}
import { Query } from './query';

export function guardMultiple(items, selector, supportMultiple) {
if (items.length > 1 && !supportMultiple) {
Expand Down Expand Up @@ -48,21 +45,6 @@ export function getRoot(node) {
return root;
}

function getAllValuesForProperty(node, property) {
let iterator = node;
let values = [];

while (isPresent(iterator)) {
if (isPresent(iterator[property])) {
values.push(iterator[property]);
}

iterator = Ceibo.parent(iterator);
}

return values;
}

/**
* @public
*
Expand All @@ -72,29 +54,7 @@ function getAllValuesForProperty(node, property) {
* @return {string} Full scope of node
*/
export function fullScope(node) {
let scopes = getAllValuesForProperty(node, 'scope');
const q = new Query(node);

return scopes.reverse().join(' ');
}

/**
* @public
*
* Returns the value of property defined on the closest ancestor of given
* node.
*
* @param {Ceibo} node - Node of the tree
* @param {string} property - Property to look for
* @return {?Object} The value of property on closest node to the given node
*/
export function findClosestValue(node, property) {
if (typeof node[property] !== 'undefined') {
return node[property];
}

let parent = Ceibo.parent(node);

if (typeof parent !== 'undefined') {
return findClosestValue(parent, property);
}
return q.toString();
}
Loading

0 comments on commit 8eef08e

Please sign in to comment.