Skip to content

Commit

Permalink
withAttr implemented (closes DevExpress#1346) (DevExpress#1349)
Browse files Browse the repository at this point in the history
* withAttr implemented

* ClientFunctions was declared outside filters

* remarks

* filterArgs
  • Loading branch information
helen-dikareva authored and kirovboris committed Dec 18, 2019
1 parent cedab4b commit 9f5b941
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 137 deletions.
111 changes: 106 additions & 5 deletions src/client-functions/selectors/add-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import { getCallsiteForMethod } from '../../errors/get-callsite';
import ClientFunctionBuilder from '../client-function-builder';
import ClientFunctionResultPromise from '../result-promise';
import { assertType, is } from '../../errors/runtime/type-assertions';
import makeRegExp from '../../utils/make-reg-exp';

const SNAPSHOT_PROPERTIES = NODE_SNAPSHOT_PROPERTIES.concat(ELEMENT_SNAPSHOT_PROPERTIES);


var filterNodes = (new ClientFunctionBuilder((nodes, filter, querySelectorRoot, originNode) => {
var filterNodes = (new ClientFunctionBuilder((nodes, filter, querySelectorRoot, originNode, ...filterArgs) => {
if (typeof filter === 'number') {
var matchingNode = filter < 0 ? nodes[nodes.length + filter] : nodes[filter];

Expand All @@ -35,15 +36,14 @@ var filterNodes = (new ClientFunctionBuilder((nodes, filter, querySelectorRoot,

if (typeof filter === 'function') {
for (var j = 0; j < nodes.length; j++) {
if (filter(nodes[j], j, originNode))
if (filter(nodes[j], j, originNode, ...filterArgs))
result.push(nodes[j]);
}
}

return result;
})).getFunction();


var expandSelectorResults = (new ClientFunctionBuilder((selector, populateDerivativeNodes) => {
var nodes = selector();

Expand Down Expand Up @@ -268,6 +268,73 @@ function createDerivativeSelectorWithFilter (getSelector, SelectorBuilder, selec
return builder.getFunction();
}

/* eslint-disable no-undef */
function hasText (node, index, originNode, textRe) {
function hasChildrenWithText (parentNode) {
var cnCount = parentNode.childNodes.length;

for (var i = 0; i < cnCount; i++) {
if (hasText(parentNode.childNodes[i], index, originNode, textRe))
return true;
}

return false;
}

// Element
if (node.nodeType === 1) {
var text = node.innerText;

// NOTE: In Firefox, <option> elements don't have `innerText`.
// So, we fallback to `textContent` in that case (see GH-861).
if (node.tagName.toLowerCase() === 'option') {
var textContent = node.textContent;

if (!text && textContent)
text = textContent;
}

return textRe.test(text);
}

// Document
if (node.nodeType === 9) {
// NOTE: latest version of Edge doesn't have `innerText` for `document`,
// `html` and `body`. So we check their children instead.
var head = node.querySelector('head');
var body = node.querySelector('body');

return hasChildrenWithText(head, textRe) || hasChildrenWithText(body, textRe);
}

// DocumentFragment
if (node.nodeType === 11)
return hasChildrenWithText(node, textRe);

return textRe.test(node.textContent);
}

function hasAttr (node, index, originNode, attrNameRe, attrValueRe) {
if (node.nodeType !== 1)
return false;

var attributes = node.attributes;
var attr = null;

for (var i = 0; i < attributes.length; i++) {
attr = attributes[i];

if (attrNameRe.test(attr.nodeName) && (!attrValueRe || attrValueRe.test(attr.nodeValue)))
return true;
}

return false;
}
/* eslint-enable no-undef */

var filterByText = convertFilterToClientFunctionIfNecessary('filter', hasText);
var filterByAttr = convertFilterToClientFunctionIfNecessary('filter', hasAttr);

function addFilterMethods (obj, getSelector, SelectorBuilder) {
obj.nth = index => {
assertType(is.number, 'nth', '"index" argument', index);
Expand All @@ -280,9 +347,43 @@ function addFilterMethods (obj, getSelector, SelectorBuilder) {
obj.withText = text => {
assertType([is.string, is.regExp], 'withText', '"text" argument', text);

var builder = new SelectorBuilder(getSelector(), { text: text }, { instantiation: 'Selector' });
var selectorFn = () => {
/* eslint-disable no-undef */
var nodes = selector();

return builder.getFunction();
if (!nodes.length)
return null;

return filterNodes(nodes, filter, document, void 0, textRe);
/* eslint-enable no-undef */
};

return createDerivativeSelectorWithFilter(getSelector, SelectorBuilder, selectorFn, filterByText, {
textRe: makeRegExp(text)
});
};

obj.withAttr = (attrName, attrValue) => {
assertType([is.string, is.regExp], 'withAttr', '"attrName" argument', attrName);

if (attrValue !== void 0)
assertType([is.string, is.regExp], 'withAttr', '"attrValue" argument', attrValue);

var selectorFn = () => {
/* eslint-disable no-undef */
var nodes = selector();

if (!nodes.length)
return null;

return filterNodes(nodes, filter, document, void 0, attrNameRe, attrValueRe);
/* eslint-enable no-undef */
};

return createDerivativeSelectorWithFilter(getSelector, SelectorBuilder, selectorFn, filterByAttr, {
attrNameRe: makeRegExp(attrName),
attrValueRe: makeRegExp(attrValue)
});
};

obj.filter = (filter, dependencies) => {
Expand Down
9 changes: 2 additions & 7 deletions src/client-functions/selectors/selector-builder.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isNil as isNullOrUndefined, merge, escapeRegExp as escapeRe } from 'lodash';
import { isNil as isNullOrUndefined, merge } from 'lodash';
import dedent from 'dedent';
import ClientFunctionBuilder from '../client-function-builder';
import { SelectorNodeTransform } from '../replicator';
Expand Down Expand Up @@ -76,19 +76,14 @@ export default class SelectorBuilder extends ClientFunctionBuilder {

getFunctionDependencies () {
var dependencies = super.getFunctionDependencies();
var text = this.options.text;
var customDOMProperties = this.options.customDOMProperties;
var customMethods = this.options.customMethods;

if (typeof text === 'string')
text = new RegExp(escapeRe(text));

return merge({}, dependencies, {
filterOptions: {
counterMode: this.options.counterMode,
collectionMode: this.options.collectionMode,
index: isNullOrUndefined(this.options.index) ? null : this.options.index,
text: text
index: isNullOrUndefined(this.options.index) ? null : this.options.index
},

boundArgs: this.options.boundArgs,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { InvalidSelectorResultError } from '../../../../../errors/test-run';
import { domUtils } from '../../../deps/testcafe-core';
import { getInnerText, getTextContent } from './sandboxed-node-properties';

// NOTE: save original ctors and methods because they may be overwritten by page code
var isArray = Array.isArray;
Expand All @@ -20,63 +18,6 @@ function isArrayOfNodes (obj) {
return true;
}

function hasText (node, textRe) {
// Element
if (node.nodeType === 1) {
var text = getInnerText(node);

// NOTE: In Firefox, <option> elements don't have `innerText`.
// So, we fallback to `textContent` in that case (see GH-861).
if (domUtils.isOptionElement(node)) {
var textContent = getTextContent(node);

if (!text && textContent)
text = textContent;
}

return textRe.test(text);
}

// Document
if (node.nodeType === 9) {
// NOTE: latest version of Edge doesn't have `innerText` for `document`,
// `html` and `body`. So we check their children instead.
var head = node.querySelector('head');
var body = node.querySelector('body');

return hasChildrenWithText(head, textRe) || hasChildrenWithText(body, textRe);
}

// DocumentFragment
if (node.nodeType === 11)
return hasChildrenWithText(node, textRe);

return textRe.test(getTextContent(node));
}

function hasChildrenWithText (node, textRe) {
var cnCount = node.childNodes.length;

for (var i = 0; i < cnCount; i++) {
if (hasText(node.childNodes[i], textRe))
return true;
}

return false;
}

function filterNodeCollectionByText (collection, textRe) {
var count = collection.length;
var filtered = [];

for (var i = 0; i < count; i++) {
if (hasText(collection[i], textRe))
filtered.push(collection[i]);
}

return filtered;
}

function getNodeByIndex (collection, index) {
return index < 0 ? collection[collection.length + index] : collection[index];
}
Expand All @@ -99,9 +40,6 @@ Object.defineProperty(window, '%testCafeSelectorFilter%', {
else
throw new InvalidSelectorResultError();

if (options.text)
filtered = filterNodeCollectionByText(filtered, options.text);

if (options.counterMode) {
if (options.index !== null)
return getNodeByIndex(filtered, options.index) ? 1 : 0;
Expand Down
5 changes: 5 additions & 0 deletions src/utils/make-reg-exp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { escapeRegExp as escapeRe } from 'lodash';

export default function makeRegExp (str) {
return typeof str === 'string' ? new RegExp(escapeRe(str)) : str;
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,10 @@
<div id="common2" class="common class1"></div>
</div>
<div class="find-parent"><div id="find-child1"><div id="find-child2"></div></div><div id="find-child3"></div><div id="find-child4"></div></div>

<div>
<div id="attr1" data-store="data-attr1" class="attr"></div>
<div id="attr2" data-store="data-attr2" class="attr"></div>
</div>
</body>
</html>
Loading

0 comments on commit 9f5b941

Please sign in to comment.