@cypress/unique-selector 1.0.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.
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ /**
7
+ * Returns the {attr} selector of the element
8
+ * @param { Element } el - The element.
9
+ * @param { String } attribute - The attribute name.
10
+ * @return { String | null } - The {attr} selector of the element.
11
+ */
12
+ var getAttributeSelector = exports.getAttributeSelector = function getAttributeSelector(el, attribute) {
13
+ var attributeValue = el.getAttribute(attribute);
14
+
15
+ if (attributeValue === null) {
16
+ return null;
17
+ }
18
+
19
+ if (attributeValue) {
20
+ // if we have value that needs quotes
21
+ return "[" + attribute + "=\"" + attributeValue + "\"]";
22
+ }
23
+
24
+ return "[" + attribute + "]";
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
@@ -23,34 +23,76 @@ var _isUnique = require('./isUnique');
23
23
 
24
24
  var _getParents = require('./getParents');
25
25
 
26
- var _getData = require('./getData');
26
+ var _getAttribute = require('./getAttribute');
27
27
 
28
28
  function _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); } } /**
29
29
  * Expose `unique`
30
30
  */
31
31
 
32
- var dataRegex = /^data-(.+)/;
32
+ var dataRegex = /^data-.+/;
33
+ var attrRegex = /^attribute:(.+)/m;
33
34
 
34
35
  /**
35
36
  * Returns all the selectors of the element
36
37
  * @param { Object } element
37
38
  * @return { Object }
38
39
  */
39
- 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
+
40
74
  var funcs = {
41
- 'tag': _getTag.getTag,
42
- '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
+ },
43
81
  'attributes': function attributes(elem) {
44
- 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);
45
86
  },
46
- 'class': _getClasses.getClassSelectors,
47
- 'id': _getID.getID,
48
- 'name': _getName.getName
87
+ 'id': function id(elem) {
88
+ return (0, _getID.getID)(elem, filters.id);
89
+ },
90
+ 'name': function name(elem) {
91
+ return (0, _getName.getName)(elem, filters.name);
92
+ }
49
93
  };
50
94
 
51
- return selectors.filter(function (selector) {
52
- return !dataRegex.test(selector);
53
- }).reduce(function (res, next) {
95
+ return nonAttributeSelectors.reduce(function (res, next) {
54
96
  res[next] = funcs[next](el);
55
97
  return res;
56
98
  }, {});
@@ -118,38 +160,40 @@ function getUniqueCombination(element, items, tag) {
118
160
  * @param { Array } options
119
161
  * @return { String }
120
162
  */
121
- function getUniqueSelector(element, selectorTypes, attributesToIgnore) {
163
+ function getUniqueSelector(element, selectorTypes, attributesToIgnore, filters) {
122
164
  var foundSelector = void 0;
123
165
 
124
- var attributes = [].concat(_toConsumableArray(element.attributes));
166
+ var elementSelectors = getAllSelectors(element, selectorTypes, attributesToIgnore, filters);
125
167
 
126
- var elementSelectors = getAllSelectors(element, selectorTypes, attributesToIgnore);
127
-
128
- var _iteratorNormalCompletion = true;
129
- var _didIteratorError = false;
130
- var _iteratorError = undefined;
168
+ var _iteratorNormalCompletion2 = true;
169
+ var _didIteratorError2 = false;
170
+ var _iteratorError2 = undefined;
131
171
 
132
172
  try {
133
- for (var _iterator = selectorTypes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
134
- var selectorType = _step.value;
173
+ for (var _iterator2 = selectorTypes[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
174
+ var selectorType = _step2.value;
135
175
 
136
176
  var selector = elementSelectors[selectorType];
137
177
 
138
178
  // if we are a data attribute
139
- if (dataRegex.test(selectorType)) {
140
- var dataSelector = (0, _getData.getData)(selectorType, attributes);
141
-
142
- // if we found a selector via data attributes
143
- if (dataSelector) {
144
- selector = dataSelector;
145
- selectorType = 'data';
179
+ var isDataAttributeSelectorType = dataRegex.test(selectorType);
180
+ var isAttributeSelectorType = !isDataAttributeSelectorType && attrRegex.test(selectorType);
181
+ if (isDataAttributeSelectorType || isAttributeSelectorType) {
182
+ var attributeToQuery = isDataAttributeSelectorType ? selectorType : selectorType.replace(attrRegex, '$1');
183
+ var attributeValue = element.getAttribute(attributeToQuery);
184
+ var attributeFilter = filters[selectorType];
185
+
186
+ // if we found a selector via attribute
187
+ if (attributeValue !== null && (!attributeFilter || attributeFilter(selectorType, attributeToQuery, attributeValue))) {
188
+ selector = (0, _getAttribute.getAttributeSelector)(element, attributeToQuery);
189
+ selectorType = 'attribute';
146
190
  }
147
191
  }
148
192
 
149
193
  if (!Boolean(selector)) continue;
150
194
 
151
195
  switch (selectorType) {
152
- case 'data':
196
+ case 'attribute':
153
197
  case 'id':
154
198
  case 'name':
155
199
  case 'tag':
@@ -175,16 +219,16 @@ function getUniqueSelector(element, selectorTypes, attributesToIgnore) {
175
219
  }
176
220
  }
177
221
  } catch (err) {
178
- _didIteratorError = true;
179
- _iteratorError = err;
222
+ _didIteratorError2 = true;
223
+ _iteratorError2 = err;
180
224
  } finally {
181
225
  try {
182
- if (!_iteratorNormalCompletion && _iterator.return) {
183
- _iterator.return();
226
+ if (!_iteratorNormalCompletion2 && _iterator2.return) {
227
+ _iterator2.return();
184
228
  }
185
229
  } finally {
186
- if (_didIteratorError) {
187
- throw _iteratorError;
230
+ if (_didIteratorError2) {
231
+ throw _iteratorError2;
188
232
  }
189
233
  }
190
234
  }
@@ -196,6 +240,15 @@ function getUniqueSelector(element, selectorTypes, attributesToIgnore) {
196
240
  * Generate unique CSS selector for given DOM element
197
241
  *
198
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.
199
252
  * @return {String}
200
253
  * @api private
201
254
  */
@@ -206,6 +259,8 @@ function unique(el) {
206
259
  selectorTypes = _options$selectorType === undefined ? ['id', 'name', 'class', 'tag', 'nth-child'] : _options$selectorType,
207
260
  _options$attributesTo = options.attributesToIgnore,
208
261
  attributesToIgnore = _options$attributesTo === undefined ? ['id', 'class', 'length'] : _options$attributesTo,
262
+ _options$filters = options.filters,
263
+ filters = _options$filters === undefined ? {} : _options$filters,
209
264
  selectorCache = options.selectorCache,
210
265
  isUniqueCache = options.isUniqueCache;
211
266
 
@@ -216,7 +271,7 @@ function unique(el) {
216
271
  var selector = selectorCache ? selectorCache.get(currentElement) : undefined;
217
272
 
218
273
  if (!selector) {
219
- selector = getUniqueSelector(currentElement, selectorTypes, attributesToIgnore);
274
+ selector = getUniqueSelector(currentElement, selectorTypes, attributesToIgnore, filters);
220
275
  if (selectorCache) {
221
276
  selectorCache.set(currentElement, selector);
222
277
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cypress/unique-selector",
3
- "version": "1.0.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",
package/lib/getData.js DELETED
@@ -1,33 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- /**
7
- * Returns the data-{dataAttr} selector of the element
8
- * @param { String } selectorType - The type of selector to return.
9
- * @param { String } attributes - The attributes of the element.
10
- * @return { String | null } - The data-{dataAttr} selector of the element.
11
- */
12
-
13
- var getData = exports.getData = function getData(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
- }
27
-
28
- return "[" + nodeName + "]";
29
- }
30
- }
31
-
32
- return null;
33
- };