@cypress/unique-selector 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,33 +1,26 @@
1
- "use strict";
1
+ 'use strict';
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  /**
7
7
  * Returns the {attr} selector of the element
8
- * @param { String } selectorType - The attribute selector to return.
9
- * @param { String } attributes - The attributes of the element.
8
+ * @param { Element } el - The element.
9
+ * @param { String } attribute - The attribute name.
10
+ * @param { Function } filter
10
11
  * @return { String | null } - The {attr} selector of the element.
11
12
  */
13
+ var getAttributeSelector = exports.getAttributeSelector = function getAttributeSelector(el, attribute, filter) {
14
+ var attributeValue = el.getAttribute(attribute);
12
15
 
13
- var getAttribute = exports.getAttribute = function getAttribute(selectorType, attributes) {
14
- for (var i = 0; i < attributes.length; i++) {
15
- // extract node name + value
16
- var _attributes$i = attributes[i],
17
- nodeName = _attributes$i.nodeName,
18
- value = _attributes$i.value;
19
-
20
- // if this matches our selector
21
-
22
- if (nodeName === selectorType) {
23
- if (value) {
24
- // if we have value that needs quotes
25
- return "[" + nodeName + "=\"" + value + "\"]";
26
- }
16
+ if (attributeValue === null || filter && !filter('attribute', attribute, attributeValue)) {
17
+ return null;
18
+ }
27
19
 
28
- return "[" + nodeName + "]";
29
- }
20
+ if (attributeValue) {
21
+ // if we have value that needs quotes
22
+ return '[' + attribute + '="' + attributeValue + '"]';
30
23
  }
31
24
 
32
- return null;
25
+ return '[' + attribute + ']';
33
26
  };
@@ -9,18 +9,20 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr
9
9
 
10
10
  /**
11
11
  * Returns the Attribute selectors of the element
12
- * @param { DOM Element } element
12
+ * @param { Element } element
13
13
  * @param { Array } array of attributes to ignore
14
+ * @param { Function } filter
14
15
  * @return { Array }
15
16
  */
16
17
  function getAttributes(el) {
17
18
  var attributesToIgnore = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ['id', 'class', 'length'];
19
+ var filter = arguments[2];
18
20
  var attributes = el.attributes;
19
21
 
20
22
  var attrs = [].concat(_toConsumableArray(attributes));
21
23
 
22
24
  return attrs.reduce(function (sum, next) {
23
- if (!(attributesToIgnore.indexOf(next.nodeName) > -1)) {
25
+ if (!(attributesToIgnore.indexOf(next.nodeName) > -1) && (!filter || filter('attribute', next.nodeName, next.value))) {
24
26
  sum.push('[' + next.nodeName + '="' + next.value + '"]');
25
27
  }
26
28
  return sum;
package/lib/getClasses.js CHANGED
@@ -11,34 +11,40 @@ require('css.escape');
11
11
  /**
12
12
  * Get class names for an element
13
13
  *
14
- * @pararm { Element } el
14
+ * @param { Element } el
15
+ * @param { Function } filter
15
16
  * @return { Array }
16
17
  */
17
- function getClasses(el) {
18
+ function getClasses(el, filter) {
18
19
  if (!el.hasAttribute('class')) {
19
20
  return [];
20
21
  }
21
22
 
22
23
  try {
23
- return Array.prototype.slice.call(el.classList);
24
+ return Array.prototype.slice.call(el.classList).filter(function (cls) {
25
+ return !filter || filter('attribute', 'class', cls);
26
+ });
24
27
  } catch (e) {
25
28
  var className = el.getAttribute('class');
26
29
 
27
30
  // remove duplicate and leading/trailing whitespaces
28
- className = className.trim().replace(/\s+/g, ' ');
31
+ className = className.trim();
29
32
 
30
- // split into separate classnames
31
- return className.split(' ');
33
+ // split into separate classnames, perform filtering
34
+ return className.split(/\s+/g).filter(function (cls) {
35
+ return !filter || filter('class', 'class', cls);
36
+ });
32
37
  }
33
38
  }
34
39
 
35
40
  /**
36
41
  * Returns the Class selectors of the element
37
42
  * @param { Object } element
43
+ * @param { Function } filter
38
44
  * @return { Array }
39
45
  */
40
- function getClassSelectors(el) {
41
- var classList = getClasses(el).filter(Boolean);
46
+ function getClassSelectors(el, filter) {
47
+ var classList = getClasses(el, filter).filter(Boolean);
42
48
  return classList.map(function (cl) {
43
49
  return '.' + CSS.escape(cl);
44
50
  });
package/lib/getID.js CHANGED
@@ -10,12 +10,13 @@ require('css.escape');
10
10
  /**
11
11
  * Returns the Tag of the element
12
12
  * @param { Object } element
13
+ * @param { Function } filter
13
14
  * @return { String }
14
15
  */
15
- function getID(el) {
16
+ function getID(el, filter) {
16
17
  var id = el.getAttribute('id');
17
18
 
18
- if (id !== null && id !== '') {
19
+ if (id !== null && id !== '' && (!filter || filter('attribute', 'id', id))) {
19
20
  return '#' + CSS.escape(id);
20
21
  }
21
22
  return null;
package/lib/getName.js CHANGED
@@ -7,12 +7,13 @@ exports.getName = getName;
7
7
  /**
8
8
  * Returns the `name` attribute of the element (if one exists)
9
9
  * @param { Object } element
10
+ * @param { Function } filter
10
11
  * @return { String }
11
12
  */
12
- function getName(el) {
13
+ function getName(el, filter) {
13
14
  var name = el.getAttribute('name');
14
15
 
15
- if (name !== null && name !== '') {
16
+ if (name !== null && name !== '' && (!filter || filter('attribute', 'name', name))) {
16
17
  return '[name="' + name + '"]';
17
18
  }
18
19
  return null;
@@ -10,9 +10,10 @@ var _isElement = require('./isElement');
10
10
  /**
11
11
  * Returns the selectors based on the position of the element relative to its siblings
12
12
  * @param { Object } element
13
+ * @param { Function } filter
13
14
  * @return { Array }
14
15
  */
15
- function getNthChild(element) {
16
+ function getNthChild(element, filter) {
16
17
  var counter = 0;
17
18
  var k = void 0;
18
19
  var sibling = void 0;
@@ -27,7 +28,7 @@ function getNthChild(element) {
27
28
  sibling = childNodes[k];
28
29
  if ((0, _isElement.isElement)(sibling)) {
29
30
  counter++;
30
- if (sibling === element) {
31
+ if (sibling === element && (!filter || filter('nth-child', 'nth-child', counter))) {
31
32
  return ':nth-child(' + counter + ')';
32
33
  }
33
34
  }
package/lib/getTag.js CHANGED
@@ -7,8 +7,15 @@ exports.getTag = getTag;
7
7
  /**
8
8
  * Returns the Tag of the element
9
9
  * @param { Object } element
10
+ * @param { Function } filter
10
11
  * @return { String }
11
12
  */
12
- function getTag(el) {
13
- return el.tagName.toLowerCase().replace(/:/g, '\\:');
13
+ function getTag(el, filter) {
14
+ var tagName = el.tagName.toLowerCase().replace(/:/g, '\\:');
15
+
16
+ if (filter && !filter('tag', 'tag', tagName)) {
17
+ return null;
18
+ }
19
+
20
+ return tagName;
14
21
  }
package/lib/index.js CHANGED
@@ -32,26 +32,76 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr
32
32
  var dataRegex = /^data-.+/;
33
33
  var attrRegex = /^attribute:(.+)/m;
34
34
 
35
+ /**
36
+ * @typedef Filter
37
+ * @type {Function}
38
+ * @param {string} type - the trait being considered ('attribute', 'tag', 'nth-child').
39
+ * @param {string} key - your trait key (for 'attribute' will be the attribute name, for others will typically be the same as 'type').
40
+ * @param {string} value - the trait value.
41
+ * @returns {boolean} whether this trait can be used when building the selector (true = allow). Defaults to 'true' if no value returned.
42
+ */
43
+
35
44
  /**
36
45
  * Returns all the selectors of the element
37
46
  * @param { Object } element
38
47
  * @return { Object }
39
48
  */
40
- function getAllSelectors(el, selectors, attributesToIgnore) {
49
+ function getAllSelectors(el, selectors, attributesToIgnore, filter) {
50
+ var consolidatedAttributesToIgnore = [].concat(_toConsumableArray(attributesToIgnore));
51
+ var nonAttributeSelectors = [];
52
+ var _iteratorNormalCompletion = true;
53
+ var _didIteratorError = false;
54
+ var _iteratorError = undefined;
55
+
56
+ try {
57
+ for (var _iterator = selectors[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
58
+ var selectorType = _step.value;
59
+
60
+ if (dataRegex.test(selectorType)) {
61
+ consolidatedAttributesToIgnore.push(selectorType);
62
+ } else if (attrRegex.test(selectorType)) {
63
+ consolidatedAttributesToIgnore.push(selectorType.replace(attrRegex, '$1'));
64
+ } else {
65
+ nonAttributeSelectors.push(selectorType);
66
+ }
67
+ }
68
+ } catch (err) {
69
+ _didIteratorError = true;
70
+ _iteratorError = err;
71
+ } finally {
72
+ try {
73
+ if (!_iteratorNormalCompletion && _iterator.return) {
74
+ _iterator.return();
75
+ }
76
+ } finally {
77
+ if (_didIteratorError) {
78
+ throw _iteratorError;
79
+ }
80
+ }
81
+ }
82
+
41
83
  var funcs = {
42
- 'tag': _getTag.getTag,
43
- 'nth-child': _getNthChild.getNthChild,
84
+ 'tag': function tag(elem) {
85
+ return (0, _getTag.getTag)(elem, filter);
86
+ },
87
+ 'nth-child': function nthChild(elem) {
88
+ return (0, _getNthChild.getNthChild)(elem, filter);
89
+ },
44
90
  'attributes': function attributes(elem) {
45
- return (0, _getAttributes.getAttributes)(elem, attributesToIgnore);
91
+ return (0, _getAttributes.getAttributes)(elem, consolidatedAttributesToIgnore, filter);
46
92
  },
47
- 'class': _getClasses.getClassSelectors,
48
- 'id': _getID.getID,
49
- 'name': _getName.getName
93
+ 'class': function _class(elem) {
94
+ return (0, _getClasses.getClassSelectors)(elem, filter);
95
+ },
96
+ 'id': function id(elem) {
97
+ return (0, _getID.getID)(elem, filter);
98
+ },
99
+ 'name': function name(elem) {
100
+ return (0, _getName.getName)(elem, filter);
101
+ }
50
102
  };
51
103
 
52
- return selectors.filter(function (selector) {
53
- return !dataRegex.test(selector) && !attrRegex.test(selector);
54
- }).reduce(function (res, next) {
104
+ return nonAttributeSelectors.reduce(function (res, next) {
55
105
  res[next] = funcs[next](el);
56
106
  return res;
57
107
  }, {});
@@ -119,20 +169,18 @@ function getUniqueCombination(element, items, tag) {
119
169
  * @param { Array } options
120
170
  * @return { String }
121
171
  */
122
- function getUniqueSelector(element, selectorTypes, attributesToIgnore) {
172
+ function getUniqueSelector(element, selectorTypes, attributesToIgnore, filter) {
123
173
  var foundSelector = void 0;
124
174
 
125
- var attributes = [].concat(_toConsumableArray(element.attributes));
175
+ var elementSelectors = getAllSelectors(element, selectorTypes, attributesToIgnore, filter);
126
176
 
127
- var elementSelectors = getAllSelectors(element, selectorTypes, attributesToIgnore);
128
-
129
- var _iteratorNormalCompletion = true;
130
- var _didIteratorError = false;
131
- var _iteratorError = undefined;
177
+ var _iteratorNormalCompletion2 = true;
178
+ var _didIteratorError2 = false;
179
+ var _iteratorError2 = undefined;
132
180
 
133
181
  try {
134
- for (var _iterator = selectorTypes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
135
- var selectorType = _step.value;
182
+ for (var _iterator2 = selectorTypes[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
183
+ var selectorType = _step2.value;
136
184
 
137
185
  var selector = elementSelectors[selectorType];
138
186
 
@@ -141,8 +189,7 @@ function getUniqueSelector(element, selectorTypes, attributesToIgnore) {
141
189
  var isAttributeSelectorType = !isDataAttributeSelectorType && attrRegex.test(selectorType);
142
190
  if (isDataAttributeSelectorType || isAttributeSelectorType) {
143
191
  var attributeToQuery = isDataAttributeSelectorType ? selectorType : selectorType.replace(attrRegex, '$1');
144
- var attributeSelector = (0, _getAttribute.getAttribute)(attributeToQuery, attributes);
145
-
192
+ var attributeSelector = (0, _getAttribute.getAttributeSelector)(element, attributeToQuery, filter);
146
193
  // if we found a selector via attribute
147
194
  if (attributeSelector) {
148
195
  selector = attributeSelector;
@@ -179,16 +226,16 @@ function getUniqueSelector(element, selectorTypes, attributesToIgnore) {
179
226
  }
180
227
  }
181
228
  } catch (err) {
182
- _didIteratorError = true;
183
- _iteratorError = err;
229
+ _didIteratorError2 = true;
230
+ _iteratorError2 = err;
184
231
  } finally {
185
232
  try {
186
- if (!_iteratorNormalCompletion && _iterator.return) {
187
- _iterator.return();
233
+ if (!_iteratorNormalCompletion2 && _iterator2.return) {
234
+ _iterator2.return();
188
235
  }
189
236
  } finally {
190
- if (_didIteratorError) {
191
- throw _iteratorError;
237
+ if (_didIteratorError2) {
238
+ throw _iteratorError2;
192
239
  }
193
240
  }
194
241
  }
@@ -200,6 +247,12 @@ function getUniqueSelector(element, selectorTypes, attributesToIgnore) {
200
247
  * Generate unique CSS selector for given DOM element
201
248
  *
202
249
  * @param {Element} el
250
+ * @param {Object} options (optional) Customize various behaviors of selector generation
251
+ * @param {String[]} options.selectorTypes Specify the set of traits to leverage when building selectors in precedence order
252
+ * @param {String[]} options.attributesToIgnore Specify a set of attributes to *not* leverage when building selectors
253
+ * @param {Filter} options.filter Provide a filter function to conditionally reject various traits when building selectors.
254
+ * @param {Map<Element, String>} options.selectorCache Provide a cache to improve performance of repeated selector generation - it is the responsibility of the caller to handle cache invalidation. Caching is performed using the input Element as key. This cache handles Element -> Selector caching.
255
+ * @param {Map<String, Boolean>} options.isUniqueCache Provide a cache to improve performance of repeated selector generation - it is the responsibility of the caller to handle cache invalidation. Caching is performed using the input Element as key. This cache handles Selector -> isUnique caching.
203
256
  * @return {String}
204
257
  * @api private
205
258
  */
@@ -210,9 +263,18 @@ function unique(el) {
210
263
  selectorTypes = _options$selectorType === undefined ? ['id', 'name', 'class', 'tag', 'nth-child'] : _options$selectorType,
211
264
  _options$attributesTo = options.attributesToIgnore,
212
265
  attributesToIgnore = _options$attributesTo === undefined ? ['id', 'class', 'length'] : _options$attributesTo,
266
+ filter = options.filter,
213
267
  selectorCache = options.selectorCache,
214
268
  isUniqueCache = options.isUniqueCache;
269
+ // If filter was provided wrap it to ensure a default value of `true` is returned if the provided function fails to return a value
215
270
 
271
+ var normalizedFilter = filter && function (type, key, value) {
272
+ var result = filter(type, key, value);
273
+ if (result === null || result === undefined) {
274
+ return true;
275
+ }
276
+ return result;
277
+ };
216
278
  var allSelectors = [];
217
279
 
218
280
  var currentElement = el;
@@ -220,7 +282,7 @@ function unique(el) {
220
282
  var selector = selectorCache ? selectorCache.get(currentElement) : undefined;
221
283
 
222
284
  if (!selector) {
223
- selector = getUniqueSelector(currentElement, selectorTypes, attributesToIgnore);
285
+ selector = getUniqueSelector(currentElement, selectorTypes, attributesToIgnore, normalizedFilter);
224
286
  if (selectorCache) {
225
287
  selectorCache.set(currentElement, selector);
226
288
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cypress/unique-selector",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "Given a DOM node, return a unique CSS selector matching only that element",
5
5
  "main": "./lib/index.js",
6
6
  "jsnext:main": "./src/index.js",