UNPKG

17.3 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6
7var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
8
9var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
10
11exports.buildPredicate = buildPredicate;
12exports.reduceTreeBySelector = reduceTreeBySelector;
13exports.reduceTreesBySelector = reduceTreesBySelector;
14
15var _rstSelectorParser = require('rst-selector-parser');
16
17var _object = require('object.values');
18
19var _object2 = _interopRequireDefault(_object);
20
21var _arrayPrototype = require('array.prototype.flat');
22
23var _arrayPrototype2 = _interopRequireDefault(_arrayPrototype);
24
25var _objectIs = require('object-is');
26
27var _objectIs2 = _interopRequireDefault(_objectIs);
28
29var _has = require('has');
30
31var _has2 = _interopRequireDefault(_has);
32
33var _RSTTraversal = require('./RSTTraversal');
34
35var _Utils = require('./Utils');
36
37var _getAdapter = require('./getAdapter');
38
39var _getAdapter2 = _interopRequireDefault(_getAdapter);
40
41function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
42
43function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
44
45// our CSS selector parser instance
46var parser = (0, _rstSelectorParser.createParser)();
47
48// Combinators that allow you to chance selectors
49var CHILD = 'childCombinator';
50var ADJACENT_SIBLING = 'adjacentSiblingCombinator';
51var GENERAL_SIBLING = 'generalSiblingCombinator';
52var DESCENDANT = 'descendantCombinator';
53
54// Selectors for targeting elements
55var SELECTOR = 'selector';
56var TYPE_SELECTOR = 'typeSelector';
57var CLASS_SELECTOR = 'classSelector';
58var ID_SELECTOR = 'idSelector';
59var ATTRIBUTE_PRESENCE = 'attributePresenceSelector';
60var ATTRIBUTE_VALUE = 'attributeValueSelector';
61// @TODO we dont support these, throw if they are used
62var PSEUDO_CLASS = 'pseudoClassSelector';
63var PSEUDO_ELEMENT = 'pseudoElementSelector';
64
65var EXACT_ATTRIBUTE_OPERATOR = '=';
66var WHITELIST_ATTRIBUTE_OPERATOR = '~=';
67var HYPHENATED_ATTRIBUTE_OPERATOR = '|=';
68var PREFIX_ATTRIBUTE_OPERATOR = '^=';
69var SUFFIX_ATTRIBUTE_OPERATOR = '$=';
70var SUBSTRING_ATTRIBUTE_OPERATOR = '*=';
71
72function unique(arr) {
73 return [].concat(_toConsumableArray(new Set(arr)));
74}
75
76/**
77 * Calls reduce on a array of nodes with the passed
78 * function, returning only unique results.
79 * @param {Function} fn
80 * @param {Array<Node>} nodes
81 */
82function uniqueReduce(fn, nodes) {
83 return unique(nodes.reduce(fn, []));
84}
85
86/**
87 * Takes a CSS selector and returns a set of tokens parsed
88 * by scalpel.
89 * @param {String} selector
90 */
91function safelyGenerateTokens(selector) {
92 try {
93 return parser.parse(selector);
94 } catch (err) {
95 throw new Error('Failed to parse selector: ' + String(selector));
96 }
97}
98
99function matchAttributeSelector(node, token) {
100 var operator = token.operator,
101 value = token.value,
102 name = token.name;
103
104 var nodeProps = (0, _Utils.propsOfNode)(node);
105 var descriptor = Object.getOwnPropertyDescriptor(nodeProps, name);
106 if (descriptor && descriptor.get) {
107 return false;
108 }
109 var nodePropValue = nodeProps[name];
110 if (typeof nodePropValue === 'undefined') {
111 return false;
112 }
113 if (token.type === ATTRIBUTE_PRESENCE) {
114 return (0, _has2['default'])(nodeProps, token.name);
115 }
116 // Only the exact value operator ("=") can match non-strings
117 if (typeof nodePropValue !== 'string' || typeof value !== 'string') {
118 if (operator !== EXACT_ATTRIBUTE_OPERATOR) {
119 return false;
120 }
121 }
122 switch (operator) {
123 /**
124 * Represents an element with the att attribute whose value is exactly "val".
125 * @example
126 * [attr="val"] matches attr="val"
127 */
128 case EXACT_ATTRIBUTE_OPERATOR:
129 return (0, _objectIs2['default'])(nodePropValue, value);
130 /**
131 * Represents an element with the att attribute whose value is a whitespace-separated
132 * list of words, one of which is exactly
133 * @example
134 * [rel~="copyright"] matches rel="copyright other"
135 */
136 case WHITELIST_ATTRIBUTE_OPERATOR:
137 return nodePropValue.split(' ').indexOf(value) !== -1;
138 /**
139 * Represents an element with the att attribute, its value either being exactly the
140 * value or beginning with the value immediately followed by "-"
141 * @example
142 * [hreflang|="en"] matches hreflang="en-US"
143 */
144 case HYPHENATED_ATTRIBUTE_OPERATOR:
145 return nodePropValue === value || nodePropValue.startsWith(String(value) + '-');
146 /**
147 * Represents an element with the att attribute whose value begins with the prefix value.
148 * If the value is the empty string then the selector does not represent anything.
149 * @example
150 * [type^="image"] matches type="imageobject"
151 */
152 case PREFIX_ATTRIBUTE_OPERATOR:
153 return value === '' ? false : nodePropValue.slice(0, value.length) === value;
154 /**
155 * Represents an element with the att attribute whose value ends with the suffix value.
156 * If the value is the empty string then the selector does not represent anything.
157 * @example
158 * [type$="image"] matches type="imageobject"
159 */
160 case SUFFIX_ATTRIBUTE_OPERATOR:
161 return value === '' ? false : nodePropValue.slice(-value.length) === value;
162 /**
163 * Represents an element with the att attribute whose value contains at least one
164 * instance of the value. If value is the empty string then the
165 * selector does not represent anything.
166 * @example
167 * [title*="hello"] matches title="well hello there"
168 */
169 case SUBSTRING_ATTRIBUTE_OPERATOR:
170 return value === '' ? false : nodePropValue.indexOf(value) !== -1;
171 default:
172 throw new Error('Enzyme::Selector: Unknown attribute selector operator "' + String(operator) + '"');
173 }
174}
175
176function matchPseudoSelector(node, token, root) {
177 var name = token.name,
178 parameters = token.parameters;
179
180 if (name === 'not') {
181 // eslint-disable-next-line no-use-before-define
182 return parameters.every(function (selector) {
183 return reduceTreeBySelector(selector, node).length === 0;
184 });
185 }
186 if (name === 'empty') {
187 return (0, _RSTTraversal.treeFilter)(node, function (n) {
188 return n !== node;
189 }).length === 0;
190 }
191 if (name === 'first-child') {
192 var _findParentNode = (0, _RSTTraversal.findParentNode)(root, node),
193 rendered = _findParentNode.rendered;
194
195 var _rendered = _slicedToArray(rendered, 1),
196 firstChild = _rendered[0];
197
198 return firstChild === node;
199 }
200 if (name === 'last-child') {
201 var _findParentNode2 = (0, _RSTTraversal.findParentNode)(root, node),
202 _rendered2 = _findParentNode2.rendered;
203
204 return _rendered2[_rendered2.length - 1] === node;
205 }
206
207 throw new TypeError('Enzyme::Selector does not support the "' + String(token.name) + '" pseudo-element or pseudo-class selectors.');
208}
209
210/**
211 * Takes a node and a token and determines if the node
212 * matches the predicate defined by the token.
213 * @param {Node} node
214 * @param {Token} token
215 */
216function nodeMatchesToken(node, token, root) {
217 if (node === null || typeof node === 'string') {
218 return false;
219 }
220 switch (token.type) {
221 /**
222 * Match against the className prop
223 * @example '.active' matches <div className='active' />
224 */
225 case CLASS_SELECTOR:
226 return (0, _RSTTraversal.hasClassName)(node, token.name);
227 /**
228 * Simple type matching
229 * @example 'div' matches <div />
230 */
231 case TYPE_SELECTOR:
232 return (0, _Utils.nodeHasType)(node, token.name);
233 /**
234 * Match against the `id` prop
235 * @example '#nav' matches <ul id="nav" />
236 */
237 case ID_SELECTOR:
238 return (0, _RSTTraversal.nodeHasId)(node, token.name);
239 /**
240 * Matches if an attribute is present, regardless
241 * of its value
242 * @example '[disabled]' matches <a disabled />
243 */
244 case ATTRIBUTE_PRESENCE:
245 return matchAttributeSelector(node, token);
246 /**
247 * Matches if an attribute is present with the
248 * provided value
249 * @example '[data-foo=foo]' matches <div data-foo="foo" />
250 */
251 case ATTRIBUTE_VALUE:
252 return matchAttributeSelector(node, token);
253 case PSEUDO_ELEMENT:
254 case PSEUDO_CLASS:
255 return matchPseudoSelector(node, token, root);
256 default:
257 throw new Error('Unknown token type: ' + String(token.type));
258 }
259}
260
261/**
262 * Returns a predicate function that checks if a
263 * node matches every token in the body of a selector
264 * token.
265 * @param {Token} token
266 */
267function buildPredicateFromToken(token, root) {
268 return function (node) {
269 return token.body.every(function (bodyToken) {
270 return nodeMatchesToken(node, bodyToken, root);
271 });
272 };
273}
274
275/**
276 * Returns whether a parsed selector is a complex selector, which
277 * is defined as a selector that contains combinators.
278 * @param {Array<Token>} tokens
279 */
280function isComplexSelector(tokens) {
281 return tokens.some(function (token) {
282 return token.type !== SELECTOR;
283 });
284}
285
286/**
287 * Takes a component constructor, object, or string representing
288 * a simple selector and returns a predicate function that can
289 * be applied to a single node.
290 * @param {Function|Object|String} selector
291 */
292function buildPredicate(selector) {
293 // If the selector is a string, parse it as a simple CSS selector
294 if (typeof selector === 'string') {
295 var tokens = safelyGenerateTokens(selector);
296 if (isComplexSelector(tokens)) {
297 throw new TypeError('This method does not support complex CSS selectors');
298 }
299 // Simple selectors only have a single selector token
300 return buildPredicateFromToken(tokens[0]);
301 }
302
303 // If the selector is an element type, check if the node's type matches
304 var adapter = (0, _getAdapter2['default'])();
305 var isElementType = adapter.isValidElementType ? adapter.isValidElementType(selector) : typeof selector === 'function';
306 if (isElementType) {
307 return function (node) {
308 return node && node.type === selector;
309 };
310 }
311 // If the selector is an non-empty object, treat the keys/values as props
312 if ((typeof selector === 'undefined' ? 'undefined' : _typeof(selector)) === 'object') {
313 if (!Array.isArray(selector) && selector !== null && Object.keys(selector).length > 0) {
314 var hasUndefinedValues = (0, _object2['default'])(selector).some(function (value) {
315 return typeof value === 'undefined';
316 });
317 if (hasUndefinedValues) {
318 throw new TypeError('Enzyme::Props can’t have `undefined` values. Try using ‘findWhere()’ instead.');
319 }
320 return function (node) {
321 return (0, _RSTTraversal.nodeMatchesObjectProps)(node, selector);
322 };
323 }
324 throw new TypeError('Enzyme::Selector does not support an array, null, or empty object as a selector');
325 }
326
327 throw new TypeError('Enzyme::Selector expects a string, object, or valid element type (Component Constructor)');
328}
329
330/**
331 * Matches only nodes which are adjacent siblings (direct next sibling)
332 * against a predicate, returning those that match.
333 * @param {Array<Node>} nodes
334 * @param {Function} predicate
335 * @param {Node} root
336 */
337function matchAdjacentSiblings(nodes, predicate, root) {
338 return nodes.reduce(function (matches, node) {
339 var parent = (0, _RSTTraversal.findParentNode)(root, node);
340 // If there's no parent, there's no siblings
341 if (!parent) {
342 return matches;
343 }
344 var parentChildren = (0, _RSTTraversal.childrenOfNode)(parent);
345 var nodeIndex = parentChildren.indexOf(node);
346 var adjacentSibling = parentChildren[nodeIndex + 1];
347 // No sibling
348 if (!adjacentSibling) {
349 return matches;
350 }
351 if (predicate(adjacentSibling)) {
352 matches.push(adjacentSibling);
353 }
354 return matches;
355 }, []);
356}
357
358/**
359 * Matches only nodes which are general siblings (any sibling *after*)
360 * against a predicate, returning those that match.
361 * @param {Array<Node>} nodes
362 * @param {Function} predicate
363 * @param {Node} root
364 */
365function matchGeneralSibling(nodes, predicate, root) {
366 return uniqueReduce(function (matches, node) {
367 var parent = (0, _RSTTraversal.findParentNode)(root, node);
368 if (!parent) {
369 return matches;
370 }
371 var parentChildren = (0, _RSTTraversal.childrenOfNode)(parent);
372 var nodeIndex = parentChildren.indexOf(node);
373 var youngerSiblings = parentChildren.slice(nodeIndex + 1);
374 return matches.concat(youngerSiblings.filter(predicate));
375 }, nodes);
376}
377
378/**
379 * Matches only nodes which are direct children (not grandchildren, etc.)
380 * against a predicate, returning those that match.
381 * @param {Array<Node>} nodes
382 * @param {Function} predicate
383 */
384function matchDirectChild(nodes, predicate) {
385 return uniqueReduce(function (matches, node) {
386 return matches.concat((0, _RSTTraversal.childrenOfNode)(node).filter(predicate));
387 }, nodes);
388}
389
390/**
391 * Matches all descendant nodes against a predicate,
392 * returning those that match.
393 * @param {Array<Node>} nodes
394 * @param {Function} predicate
395 */
396function matchDescendant(nodes, predicate) {
397 return uniqueReduce(function (matches, node) {
398 return matches.concat((0, _RSTTraversal.treeFilter)(node, predicate));
399 }, (0, _arrayPrototype2['default'])(nodes.map(_RSTTraversal.childrenOfNode)));
400}
401
402/**
403 * Takes an RST and reduces it to a set of nodes matching
404 * the selector. The selector can be a simple selector, which
405 * is handled by `buildPredicate`, or a complex CSS selector which
406 * reduceTreeBySelector parses and reduces the tree based on the combinators.
407 * @param {Function|Object|String} selector
408 * @param {RSTNode} root
409 */
410function reduceTreeBySelector(selector, root) {
411 if (typeof selector === 'function' || (typeof selector === 'undefined' ? 'undefined' : _typeof(selector)) === 'object') {
412 return (0, _RSTTraversal.treeFilter)(root, buildPredicate(selector));
413 }
414
415 var results = [];
416 if (typeof selector === 'string') {
417 var tokens = safelyGenerateTokens(selector);
418 var index = 0;
419 while (index < tokens.length) {
420 var token = tokens[index];
421 /**
422 * There are two types of tokens in a CSS selector:
423 *
424 * 1. Selector tokens. These target nodes directly, like
425 * type or attribute selectors. These are easy to apply
426 * because we can traverse the tree and return only
427 * the nodes that match the predicate.
428 *
429 * 2. Combinator tokens. These tokens chain together
430 * selector nodes. For example > for children, or +
431 * for adjacent siblings. These are harder to match
432 * as we have to track where in the tree we are
433 * to determine if a selector node applies or not.
434 */
435 if (token.type === SELECTOR) {
436 var predicate = buildPredicateFromToken(token, root);
437 results = results.concat((0, _RSTTraversal.treeFilter)(root, predicate));
438 } else {
439 // We can assume there always all previously matched tokens since selectors
440 // cannot start with combinators.
441 var type = token.type;
442 // We assume the next token is a selector, so move the index
443 // forward and build the predicate.
444
445 index += 1;
446 var _predicate = buildPredicateFromToken(tokens[index], root);
447 // We match against only the nodes which have already been matched,
448 // since a combinator is meant to refine a previous selector.
449 switch (type) {
450 // The + combinator
451 case ADJACENT_SIBLING:
452 results = matchAdjacentSiblings(results, _predicate, root);
453 break;
454 // The ~ combinator
455 case GENERAL_SIBLING:
456 results = matchGeneralSibling(results, _predicate, root);
457 break;
458 // The > combinator
459 case CHILD:
460 results = matchDirectChild(results, _predicate);
461 break;
462 // The ' ' (whitespace) combinator
463 case DESCENDANT:
464 {
465 results = matchDescendant(results, _predicate);
466 break;
467 }
468 default:
469 throw new Error('Unknown combinator selector: ' + String(type));
470 }
471 }
472 index += 1;
473 }
474 } else {
475 throw new TypeError('Enzyme::Selector expects a string, object, or Component Constructor');
476 }
477 return results;
478}
479
480function reduceTreesBySelector(selector, roots) {
481 var results = roots.map(function (n) {
482 return reduceTreeBySelector(selector, n);
483 });
484 return unique((0, _arrayPrototype2['default'])(results, 1));
485}
\No newline at end of file