@cypress/unique-selector 1.1.0 → 1.2.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.
@@ -5,29 +5,21 @@ Object.defineProperty(exports, "__esModule", {
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
10
  * @return { String | null } - The {attr} selector of the element.
11
11
  */
12
+ var getAttributeSelector = exports.getAttributeSelector = function getAttributeSelector(el, attribute) {
13
+ var attributeValue = el.getAttribute(attribute);
12
14
 
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
- }
15
+ if (attributeValue === null) {
16
+ return null;
17
+ }
27
18
 
28
- return "[" + nodeName + "]";
29
- }
19
+ if (attributeValue) {
20
+ // if we have value that needs quotes
21
+ return "[" + attribute + "=\"" + attributeValue + "\"]";
30
22
  }
31
23
 
32
- return null;
24
+ return "[" + attribute + "]";
33
25
  };
@@ -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('attributes', 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('class', '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('id', '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('name', '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
@@ -37,21 +37,62 @@ var attrRegex = /^attribute:(.+)/m;
37
37
  * @param { Object } element
38
38
  * @return { Object }
39
39
  */
40
- function getAllSelectors(el, selectors, attributesToIgnore) {
40
+ function getAllSelectors(el, selectors, attributesToIgnore, filters) {
41
+ var consolidatedAttributesToIgnore = [].concat(_toConsumableArray(attributesToIgnore));
42
+ var nonAttributeSelectors = [];
43
+ var _iteratorNormalCompletion = true;
44
+ var _didIteratorError = false;
45
+ var _iteratorError = undefined;
46
+
47
+ try {
48
+ for (var _iterator = selectors[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
49
+ var selectorType = _step.value;
50
+
51
+ if (dataRegex.test(selectorType)) {
52
+ consolidatedAttributesToIgnore.push(selectorType);
53
+ } else if (attrRegex.test(selectorType)) {
54
+ consolidatedAttributesToIgnore.push(selectorType.replace(attrRegex, '$1'));
55
+ } else {
56
+ nonAttributeSelectors.push(selectorType);
57
+ }
58
+ }
59
+ } catch (err) {
60
+ _didIteratorError = true;
61
+ _iteratorError = err;
62
+ } finally {
63
+ try {
64
+ if (!_iteratorNormalCompletion && _iterator.return) {
65
+ _iterator.return();
66
+ }
67
+ } finally {
68
+ if (_didIteratorError) {
69
+ throw _iteratorError;
70
+ }
71
+ }
72
+ }
73
+
41
74
  var funcs = {
42
- 'tag': _getTag.getTag,
43
- 'nth-child': _getNthChild.getNthChild,
75
+ 'tag': function tag(elem) {
76
+ return (0, _getTag.getTag)(elem, filters.tag);
77
+ },
78
+ 'nth-child': function nthChild(elem) {
79
+ return (0, _getNthChild.getNthChild)(elem, filters.nthChild);
80
+ },
44
81
  'attributes': function attributes(elem) {
45
- return (0, _getAttributes.getAttributes)(elem, attributesToIgnore);
82
+ return (0, _getAttributes.getAttributes)(elem, consolidatedAttributesToIgnore, filters.attributes);
83
+ },
84
+ 'class': function _class(elem) {
85
+ return (0, _getClasses.getClassSelectors)(elem, filters.class);
86
+ },
87
+ 'id': function id(elem) {
88
+ return (0, _getID.getID)(elem, filters.id);
46
89
  },
47
- 'class': _getClasses.getClassSelectors,
48
- 'id': _getID.getID,
49
- 'name': _getName.getName
90
+ 'name': function name(elem) {
91
+ return (0, _getName.getName)(elem, filters.name);
92
+ }
50
93
  };
51
94
 
52
- return selectors.filter(function (selector) {
53
- return !dataRegex.test(selector) && !attrRegex.test(selector);
54
- }).reduce(function (res, next) {
95
+ return nonAttributeSelectors.reduce(function (res, next) {
55
96
  res[next] = funcs[next](el);
56
97
  return res;
57
98
  }, {});
@@ -119,20 +160,18 @@ function getUniqueCombination(element, items, tag) {
119
160
  * @param { Array } options
120
161
  * @return { String }
121
162
  */
122
- function getUniqueSelector(element, selectorTypes, attributesToIgnore) {
163
+ function getUniqueSelector(element, selectorTypes, attributesToIgnore, filters) {
123
164
  var foundSelector = void 0;
124
165
 
125
- var attributes = [].concat(_toConsumableArray(element.attributes));
126
-
127
- var elementSelectors = getAllSelectors(element, selectorTypes, attributesToIgnore);
166
+ var elementSelectors = getAllSelectors(element, selectorTypes, attributesToIgnore, filters);
128
167
 
129
- var _iteratorNormalCompletion = true;
130
- var _didIteratorError = false;
131
- var _iteratorError = undefined;
168
+ var _iteratorNormalCompletion2 = true;
169
+ var _didIteratorError2 = false;
170
+ var _iteratorError2 = undefined;
132
171
 
133
172
  try {
134
- for (var _iterator = selectorTypes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
135
- var selectorType = _step.value;
173
+ for (var _iterator2 = selectorTypes[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
174
+ var selectorType = _step2.value;
136
175
 
137
176
  var selector = elementSelectors[selectorType];
138
177
 
@@ -141,11 +180,12 @@ function getUniqueSelector(element, selectorTypes, attributesToIgnore) {
141
180
  var isAttributeSelectorType = !isDataAttributeSelectorType && attrRegex.test(selectorType);
142
181
  if (isDataAttributeSelectorType || isAttributeSelectorType) {
143
182
  var attributeToQuery = isDataAttributeSelectorType ? selectorType : selectorType.replace(attrRegex, '$1');
144
- var attributeSelector = (0, _getAttribute.getAttribute)(attributeToQuery, attributes);
183
+ var attributeValue = element.getAttribute(attributeToQuery);
184
+ var attributeFilter = filters[selectorType];
145
185
 
146
186
  // if we found a selector via attribute
147
- if (attributeSelector) {
148
- selector = attributeSelector;
187
+ if (attributeValue !== null && (!attributeFilter || attributeFilter(selectorType, attributeToQuery, attributeValue))) {
188
+ selector = (0, _getAttribute.getAttributeSelector)(element, attributeToQuery);
149
189
  selectorType = 'attribute';
150
190
  }
151
191
  }
@@ -179,16 +219,16 @@ function getUniqueSelector(element, selectorTypes, attributesToIgnore) {
179
219
  }
180
220
  }
181
221
  } catch (err) {
182
- _didIteratorError = true;
183
- _iteratorError = err;
222
+ _didIteratorError2 = true;
223
+ _iteratorError2 = err;
184
224
  } finally {
185
225
  try {
186
- if (!_iteratorNormalCompletion && _iterator.return) {
187
- _iterator.return();
226
+ if (!_iteratorNormalCompletion2 && _iterator2.return) {
227
+ _iterator2.return();
188
228
  }
189
229
  } finally {
190
- if (_didIteratorError) {
191
- throw _iteratorError;
230
+ if (_didIteratorError2) {
231
+ throw _iteratorError2;
192
232
  }
193
233
  }
194
234
  }
@@ -200,6 +240,15 @@ function getUniqueSelector(element, selectorTypes, attributesToIgnore) {
200
240
  * Generate unique CSS selector for given DOM element
201
241
  *
202
242
  * @param {Element} el
243
+ * @param {Object} options (optional) Customize various behaviors of selector generation
244
+ * @param {String[]} options.selectorTypes Specify the set of traits to leverage when building selectors in precedence order
245
+ * @param {String[]} options.attributesToIgnore Specify a set of attributes to *not* leverage when building selectors
246
+ * @param {Object} options.filters Specify a set of filter functions to conditionally reject various traits when building selectors. Keys correspond to a `selectorTypes` entry, values should be a function accepting three parameters:
247
+ * * selectorType: The selector type/category being generated
248
+ * * key: The key being evaluated - this will typically match the `selectorType` except in aggregate types like `attributes`
249
+ * * value: The value to consider. Returning `true` will allow its use in selector generation, `false` will prevent.
250
+ * @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.
251
+ * @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
252
  * @return {String}
204
253
  * @api private
205
254
  */
@@ -210,6 +259,8 @@ function unique(el) {
210
259
  selectorTypes = _options$selectorType === undefined ? ['id', 'name', 'class', 'tag', 'nth-child'] : _options$selectorType,
211
260
  _options$attributesTo = options.attributesToIgnore,
212
261
  attributesToIgnore = _options$attributesTo === undefined ? ['id', 'class', 'length'] : _options$attributesTo,
262
+ _options$filters = options.filters,
263
+ filters = _options$filters === undefined ? {} : _options$filters,
213
264
  selectorCache = options.selectorCache,
214
265
  isUniqueCache = options.isUniqueCache;
215
266
 
@@ -220,7 +271,7 @@ function unique(el) {
220
271
  var selector = selectorCache ? selectorCache.get(currentElement) : undefined;
221
272
 
222
273
  if (!selector) {
223
- selector = getUniqueSelector(currentElement, selectorTypes, attributesToIgnore);
274
+ selector = getUniqueSelector(currentElement, selectorTypes, attributesToIgnore, filters);
224
275
  if (selectorCache) {
225
276
  selectorCache.set(currentElement, selector);
226
277
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cypress/unique-selector",
3
- "version": "1.1.0",
3
+ "version": "1.2.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",