@cypress/unique-selector 0.0.5

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.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ unique-selector
2
+ ===============
3
+
4
+ [![Build Status](https://travis-ci.org/ericclemmons/unique-selector.png)](https://travis-ci.org/ericclemmons/unique-selector)
5
+ [![CocoaPods](https://img.shields.io/cocoapods/l/AFNetworking.svg)]()
6
+ [![semantic-versioning](https://img.shields.io/badge/semantic%20-versioning-green.svg)]()
7
+
8
+ Given a DOM node, return a unique CSS selector matching only that element.
9
+ This is particularly useful when tracking in custom variables in analytics:
10
+
11
+
12
+ document.addEventListener('click', function(event) {
13
+ var selector = unique(event.target);
14
+
15
+ _gaq.push(['_trackEvent', 'Engagement', 'Click', selector]);
16
+ }, false);
17
+
18
+
19
+ Installation
20
+ ------------
21
+
22
+ [![NPM](https://nodei.co/npm/unique-selector.png?mini=true)](https://nodei.co/npm/unique-selector/)
23
+
24
+ Options
25
+ ------------
26
+ e.g.1 DomElement = `<span id="test"></span>`
27
+
28
+ ```
29
+ import unique from 'unique-selector';
30
+
31
+ // Optional Options
32
+ options = {
33
+ // Array of selector types based on which the unique selector will generate
34
+ selectorTypes : [ 'ID', 'Class', 'Tag', 'NthChild' ]
35
+ }
36
+
37
+ unique( DomElement, options ); // #test
38
+ ```
39
+
40
+ e.g.2 DomElement = `<span test="2"></span>`
41
+
42
+ ```
43
+ import unique from 'unique-selector';
44
+
45
+ // Optional Options
46
+ options = {
47
+ // Array of selector types based on which the unique selector will be generate
48
+ selectorTypes : [ 'Attributes' ]
49
+ }
50
+
51
+ unique( DomElement, options ); // [test="2"]
52
+ ```
53
+
54
+
55
+ Tests
56
+ -----
57
+
58
+ $ npm run test
59
+
60
+
61
+ Contributing
62
+ -----
63
+ Feel free to open issues, make suggestions or send PRs.
64
+ This project adheres to the Contributor Covenant [code of conduct](http://contributor-covenant.org/). By participating, you are expected to uphold this code.
65
+
66
+
67
+ Contact
68
+ -----
69
+
70
+ Avraam Mavridis : [@avraamakis](https://twitter.com/avraamakis)
71
+
72
+ Eric Clemmons : [@ericclemmons](https://twitter.com/ericclemmons)
73
+
74
+
75
+ Releases
76
+ --------
77
+ - v0.1.0
78
+
79
+ - Big refactor/rewrite using es6
80
+ - More test + Change the test script
81
+ - Script to compile es6 to es5 using babel
82
+ - Ability to pass options for the **selectorTypes** based on which the unique selector will be generated, see the options
83
+
84
+
85
+ - v0.0.4
86
+
87
+ - Handle extra whitespace in `className` ([#9](https://github.com/ericclemmons/unique-selector/pull/9))
88
+
89
+ - v0.0.3
90
+
91
+ - Add support for various `<form>` elements ([#2](https://github.com/ericclemmons/unique-selector/issues/2))
92
+
93
+ - v0.0.2
94
+
95
+ - Fix bug with `nth-child` calculation
96
+
97
+ - v0.0.1
98
+
99
+ - Initial release
100
+
101
+
102
+
103
+ License
104
+ -------
105
+
106
+ MIT
107
+
108
+
109
+ [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/AvraamMavridis/unique-selector/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getAttributes = getAttributes;
7
+
8
+ 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); } }
9
+
10
+ /**
11
+ * Returns the Attribute selectors of the element
12
+ * @param { DOM Element } element
13
+ * @param { Array } array of attributes to ignore
14
+ * @return { Array }
15
+ */
16
+ function getAttributes(el) {
17
+ var attributesToIgnore = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ['id', 'class', 'length'];
18
+ var attributes = el.attributes;
19
+
20
+ var attrs = [].concat(_toConsumableArray(attributes));
21
+
22
+ return attrs.reduce(function (sum, next) {
23
+ if (!(attributesToIgnore.indexOf(next.nodeName) > -1)) {
24
+ sum.push('[' + next.nodeName + '="' + next.value + '"]');
25
+ }
26
+ return sum;
27
+ }, []);
28
+ }
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getClasses = getClasses;
7
+ exports.getClassSelectors = getClassSelectors;
8
+
9
+ require('css.escape');
10
+
11
+ /**
12
+ * Get class names for an element
13
+ *
14
+ * @pararm { Element } el
15
+ * @return { Array }
16
+ */
17
+ function getClasses(el) {
18
+ if (!el.hasAttribute('class')) {
19
+ return [];
20
+ }
21
+
22
+ try {
23
+ return Array.prototype.slice.call(el.classList);
24
+ } catch (e) {
25
+ var className = el.getAttribute('class');
26
+
27
+ // remove duplicate and leading/trailing whitespaces
28
+ className = className.trim().replace(/\s+/g, ' ');
29
+
30
+ // split into separate classnames
31
+ return className.split(' ');
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Returns the Class selectors of the element
37
+ * @param { Object } element
38
+ * @return { Array }
39
+ */
40
+ function getClassSelectors(el) {
41
+ var classList = getClasses(el).filter(Boolean);
42
+ return classList.map(function (cl) {
43
+ return '.' + CSS.escape(cl);
44
+ });
45
+ }
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getCombinations = getCombinations;
7
+ /**
8
+ * Recursively combinate items.
9
+ * @param { Array } result
10
+ * @param { Array } items
11
+ * @param { Array } data
12
+ * @param { Number } start
13
+ * @param { Number } end
14
+ * @param { Number } index
15
+ * @param { Number } k
16
+ */
17
+ function kCombinations(result, items, data, start, end, index, k) {
18
+ if (index === k) {
19
+ result.push(data.slice(0, index).join(''));
20
+ return;
21
+ }
22
+
23
+ for (var i = start; i <= end && end - i + 1 >= k - index; ++i) {
24
+ data[index] = items[i];
25
+ kCombinations(result, items, data, i + 1, end, index + 1, k);
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Returns all the possible selector combinations
31
+ * @param { Array } items
32
+ * @param { Number } k
33
+ * @return { Array }
34
+ */
35
+ function getCombinations(items, k) {
36
+ var result = [],
37
+ n = items.length,
38
+ data = [];
39
+
40
+ for (var l = 1; l <= k; ++l) {
41
+ kCombinations(result, items, data, 0, n - 1, 0, l);
42
+ }
43
+
44
+ return result;
45
+ }
package/lib/getData.js ADDED
@@ -0,0 +1,33 @@
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
+ };
package/lib/getID.js ADDED
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getID = getID;
7
+
8
+ require('css.escape');
9
+
10
+ /**
11
+ * Returns the Tag of the element
12
+ * @param { Object } element
13
+ * @return { String }
14
+ */
15
+ function getID(el) {
16
+ var id = el.getAttribute('id');
17
+
18
+ if (id !== null && id !== '') {
19
+ return '#' + CSS.escape(id);
20
+ }
21
+ return null;
22
+ }
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getNthChild = getNthChild;
7
+
8
+ var _isElement = require('./isElement');
9
+
10
+ /**
11
+ * Returns the selectors based on the position of the element relative to its siblings
12
+ * @param { Object } element
13
+ * @return { Array }
14
+ */
15
+ function getNthChild(element) {
16
+ var counter = 0;
17
+ var k = void 0;
18
+ var sibling = void 0;
19
+ var parentNode = element.parentNode;
20
+
21
+
22
+ if (Boolean(parentNode)) {
23
+ var childNodes = parentNode.childNodes;
24
+
25
+ var len = childNodes.length;
26
+ for (k = 0; k < len; k++) {
27
+ sibling = childNodes[k];
28
+ if ((0, _isElement.isElement)(sibling)) {
29
+ counter++;
30
+ if (sibling === element) {
31
+ return ':nth-child(' + counter + ')';
32
+ }
33
+ }
34
+ }
35
+ }
36
+ return null;
37
+ }
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getParents = getParents;
7
+
8
+ var _isElement = require('./isElement');
9
+
10
+ /**
11
+ * Returns all the element and all of its parents
12
+ * @param { DOM Element }
13
+ * @return { Array of DOM elements }
14
+ */
15
+ function getParents(el) {
16
+ var parents = [];
17
+ var currentElement = el;
18
+ while ((0, _isElement.isElement)(currentElement)) {
19
+ parents.push(currentElement);
20
+ currentElement = currentElement.parentNode;
21
+ }
22
+
23
+ return parents;
24
+ }
package/lib/getTag.js ADDED
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getTag = getTag;
7
+ /**
8
+ * Returns the Tag of the element
9
+ * @param { Object } element
10
+ * @return { String }
11
+ */
12
+ function getTag(el) {
13
+ return el.tagName.toLowerCase().replace(/:/g, '\\:');
14
+ }
package/lib/index.js ADDED
@@ -0,0 +1,238 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = unique;
7
+
8
+ var _getID = require('./getID');
9
+
10
+ var _getClasses = require('./getClasses');
11
+
12
+ var _getCombinations = require('./getCombinations');
13
+
14
+ var _getAttributes = require('./getAttributes');
15
+
16
+ var _getNthChild = require('./getNthChild');
17
+
18
+ var _getTag = require('./getTag');
19
+
20
+ var _isUnique = require('./isUnique');
21
+
22
+ var _getParents = require('./getParents');
23
+
24
+ var _getData = require('./getData');
25
+
26
+ 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); } } /**
27
+ * Expose `unique`
28
+ */
29
+
30
+ var dataRegex = /^data-(.+)/;
31
+
32
+ /**
33
+ * Returns all the selectors of the element
34
+ * @param { Object } element
35
+ * @return { Object }
36
+ */
37
+ function getAllSelectors(el, selectors, attributesToIgnore) {
38
+ var funcs = {
39
+ 'tag': _getTag.getTag,
40
+ 'nth-child': _getNthChild.getNthChild,
41
+ 'attributes': function attributes(elem) {
42
+ return (0, _getAttributes.getAttributes)(elem, attributesToIgnore);
43
+ },
44
+ 'class': _getClasses.getClassSelectors,
45
+ 'id': _getID.getID
46
+ };
47
+
48
+ return selectors.filter(function (selector) {
49
+ return !dataRegex.test(selector);
50
+ }).reduce(function (res, next) {
51
+ res[next] = funcs[next](el);
52
+ return res;
53
+ }, {});
54
+ }
55
+
56
+ /**
57
+ * Tests uniqueNess of the element inside its parent
58
+ * @param { Object } element
59
+ * @param { String } Selectors
60
+ * @return { Boolean }
61
+ */
62
+ function testUniqueness(element, selector) {
63
+ var parentNode = element.parentNode;
64
+
65
+ try {
66
+ var elements = parentNode.querySelectorAll(selector);
67
+ return elements.length === 1 && elements[0] === element;
68
+ } catch (e) {
69
+ return false;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Tests all selectors for uniqueness and returns the first unique selector.
75
+ * @param { Object } element
76
+ * @param { Array } selectors
77
+ * @return { String }
78
+ */
79
+ function getFirstUnique(element, selectors) {
80
+ return selectors.find(testUniqueness.bind(null, element));
81
+ }
82
+
83
+ /**
84
+ * Checks all the possible selectors of an element to find one unique and return it
85
+ * @param { Object } element
86
+ * @param { Array } items
87
+ * @param { String } tag
88
+ * @return { String }
89
+ */
90
+ function getUniqueCombination(element, items, tag) {
91
+ var combinations = (0, _getCombinations.getCombinations)(items, 3),
92
+ firstUnique = getFirstUnique(element, combinations);
93
+
94
+ if (Boolean(firstUnique)) {
95
+ return firstUnique;
96
+ }
97
+
98
+ if (Boolean(tag)) {
99
+ combinations = combinations.map(function (combination) {
100
+ return tag + combination;
101
+ });
102
+ firstUnique = getFirstUnique(element, combinations);
103
+
104
+ if (Boolean(firstUnique)) {
105
+ return firstUnique;
106
+ }
107
+ }
108
+
109
+ return null;
110
+ }
111
+
112
+ /**
113
+ * Returns a uniqueSelector based on the passed options
114
+ * @param { DOM } element
115
+ * @param { Array } options
116
+ * @return { String }
117
+ */
118
+ function getUniqueSelector(element, selectorTypes, attributesToIgnore) {
119
+ var foundSelector = void 0;
120
+
121
+ var attributes = [].concat(_toConsumableArray(element.attributes));
122
+
123
+ var elementSelectors = getAllSelectors(element, selectorTypes, attributesToIgnore);
124
+
125
+ var _iteratorNormalCompletion = true;
126
+ var _didIteratorError = false;
127
+ var _iteratorError = undefined;
128
+
129
+ try {
130
+ for (var _iterator = selectorTypes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
131
+ var selectorType = _step.value;
132
+
133
+ var selector = elementSelectors[selectorType];
134
+
135
+ // if we are a data attribute
136
+ if (dataRegex.test(selectorType)) {
137
+ var dataSelector = (0, _getData.getData)(selectorType, attributes);
138
+
139
+ // if we found a selector via data attributes
140
+ if (dataSelector) {
141
+ selector = dataSelector;
142
+ selectorType = 'data';
143
+ }
144
+ }
145
+
146
+ if (!Boolean(selector)) continue;
147
+
148
+ switch (selectorType) {
149
+ case 'data':
150
+ case 'id':
151
+ case 'tag':
152
+ if (testUniqueness(element, selector)) {
153
+ return selector;
154
+ }
155
+ break;
156
+ case 'class':
157
+ case 'attributes':
158
+ if (selector.length) {
159
+ foundSelector = getUniqueCombination(element, selector, elementSelectors.tag);
160
+ if (foundSelector) {
161
+ return foundSelector;
162
+ }
163
+ }
164
+ break;
165
+
166
+ case 'nth-child':
167
+ return selector;
168
+
169
+ default:
170
+ break;
171
+ }
172
+ }
173
+ } catch (err) {
174
+ _didIteratorError = true;
175
+ _iteratorError = err;
176
+ } finally {
177
+ try {
178
+ if (!_iteratorNormalCompletion && _iterator.return) {
179
+ _iterator.return();
180
+ }
181
+ } finally {
182
+ if (_didIteratorError) {
183
+ throw _iteratorError;
184
+ }
185
+ }
186
+ }
187
+
188
+ return '*';
189
+ }
190
+
191
+ /**
192
+ * Generate unique CSS selector for given DOM element
193
+ *
194
+ * @param {Element} el
195
+ * @return {String}
196
+ * @api private
197
+ */
198
+
199
+ function unique(el) {
200
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
201
+ var _options$selectorType = options.selectorTypes,
202
+ selectorTypes = _options$selectorType === undefined ? ['id', 'class', 'tag', 'nth-child'] : _options$selectorType,
203
+ _options$attributesTo = options.attributesToIgnore,
204
+ attributesToIgnore = _options$attributesTo === undefined ? ['id', 'class', 'length'] : _options$attributesTo,
205
+ selectorCache = options.selectorCache,
206
+ isUniqueCache = options.isUniqueCache;
207
+
208
+ var allSelectors = [];
209
+
210
+ var currentElement = el;
211
+ while (currentElement) {
212
+ var selector = selectorCache ? selectorCache.get(currentElement) : undefined;
213
+
214
+ if (!selector) {
215
+ selector = getUniqueSelector(currentElement, selectorTypes, attributesToIgnore);
216
+ if (selectorCache) {
217
+ selectorCache.set(currentElement, selector);
218
+ }
219
+ }
220
+
221
+ allSelectors.unshift(selector);
222
+ var maybeUniqueSelector = allSelectors.join(' > ');
223
+ var isUniqueSelector = isUniqueCache ? isUniqueCache.get(maybeUniqueSelector) : undefined;
224
+ if (isUniqueSelector === undefined) {
225
+ isUniqueSelector = (0, _isUnique.isUnique)(el, maybeUniqueSelector);
226
+ if (isUniqueCache) {
227
+ isUniqueCache.set(maybeUniqueSelector, isUniqueSelector);
228
+ }
229
+ }
230
+
231
+ if (isUniqueSelector) {
232
+ return maybeUniqueSelector;
233
+ }
234
+ currentElement = currentElement.parentNode;
235
+ }
236
+
237
+ return null;
238
+ }
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+
7
+ var _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
+
9
+ exports.isElement = isElement;
10
+ /**
11
+ * Determines if the passed el is a DOM element
12
+ */
13
+ function isElement(el) {
14
+ var isElem = void 0;
15
+
16
+ if ((typeof HTMLElement === 'undefined' ? 'undefined' : _typeof(HTMLElement)) === 'object') {
17
+ isElem = el instanceof HTMLElement;
18
+ } else {
19
+ isElem = !!el && (typeof el === 'undefined' ? 'undefined' : _typeof(el)) === 'object' && el.nodeType === 1 && typeof el.nodeName === 'string';
20
+ }
21
+ return isElem;
22
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isUnique = isUnique;
7
+ /**
8
+ * Checks if the selector is unique
9
+ * @param { Object } element
10
+ * @param { String } selector
11
+ * @return { Array }
12
+ */
13
+ function isUnique(el, selector) {
14
+ if (!Boolean(selector)) return false;
15
+ try {
16
+ var elems = el.ownerDocument.querySelectorAll(selector);
17
+ return elems.length === 1 && elems[0] === el;
18
+ } catch (e) {
19
+ return false;
20
+ }
21
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@cypress/unique-selector",
3
+ "version": "0.0.5",
4
+ "description": "Given a DOM node, return a unique CSS selector matching only that element",
5
+ "main": "./lib/index.js",
6
+ "jsnext:main": "./src/index.js",
7
+ "directories": {
8
+ "test": "test"
9
+ },
10
+ "scripts": {
11
+ "build": "npm run compile",
12
+ "test": "npm run compile && mocha --ignore-leaks",
13
+ "compile": "babel -d lib/ src/",
14
+ "prepublishOnly": "npm run compile",
15
+ "watch": "npm-scripts-watcher"
16
+ },
17
+ "watch": {
18
+ "src/**/*.js": [
19
+ "compile"
20
+ ]
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git://github.com/cypress-io/unique-selector.git"
25
+ },
26
+ "keywords": [
27
+ "dom",
28
+ "html",
29
+ "css",
30
+ "selector",
31
+ "event"
32
+ ],
33
+ "author": "Cypress",
34
+ "license": "MIT",
35
+ "devDependencies": {
36
+ "babel-cli": "^6.26.0",
37
+ "babel-core": "^6.26.3",
38
+ "babel-preset-latest": "^6.24.1",
39
+ "babel-register": "^6.26.0",
40
+ "chai": "^4.3.4",
41
+ "component": "~1.1.0",
42
+ "eslint": "^8.0.1",
43
+ "eslint-config-airbnb": "^18.2.1",
44
+ "eslint-config-airbnb-lite": "^1.0.4",
45
+ "eslint-plugin-react": "^7.26.1",
46
+ "jquery": "^3.6.0",
47
+ "jsdom": "^18.0.0",
48
+ "mocha": "~9.1.3",
49
+ "mocha-jsdom": "^2.0.0",
50
+ "mocha-phantomjs": "~4.1.0",
51
+ "npm-scripts-watcher": "^1.0.2"
52
+ },
53
+ "dependencies": {
54
+ "css.escape": "^1.5.1"
55
+ },
56
+ "files": [
57
+ "lib"
58
+ ]
59
+ }