@cypress/unique-selector 2.1.1 → 2.1.3

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 CHANGED
@@ -1,115 +1,196 @@
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)]()
1
+ # unique-selector
7
2
 
8
3
  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
4
 
5
+ Selector uniqueness is determined based on the given element's root node. Elements rendered within Shadow DOM will derive a selector unique within the associated ShadowRoot context. Otherwise, a selector unique within an element's owning document will be derived.
11
6
 
12
- document.addEventListener('click', function(event) {
13
- var selector = unique(event.target);
7
+ ## Installation
14
8
 
15
- _gaq.push(['_trackEvent', 'Engagement', 'Click', selector]);
16
- }, false);
9
+ ![NPM Version](https://img.shields.io/npm/v/%40cypress%2Funique-selector)
17
10
 
18
- Selector uniqueness is determined based on the given element's root node. Elements rendered within Shadow DOM will derive a selector unique within the associated ShadowRoot context. Otherwise, a selector unique within an element's owning document will be derived.
11
+ ```bash
12
+ npm install @cypress/unique-selector
13
+ ```
19
14
 
20
- Installation
21
- ------------
15
+ ## API
22
16
 
23
- [![NPM](https://nodei.co/npm/unique-selector.png?mini=true)](https://nodei.co/npm/unique-selector/)
17
+ ### `unique(element, options)`
24
18
 
25
- Options
26
- ------------
27
- e.g.1 DomElement = `<span id="test"></span>`
19
+ Generates a unique CSS selector for the given DOM element.
28
20
 
29
- ```
30
- import unique from 'unique-selector';
21
+ #### Parameters
31
22
 
32
- // Optional Options
33
- options = {
34
- // Array of selector types based on which the unique selector will generate
35
- selectorTypes : [ 'ID', 'Class', 'Tag', 'NthChild' ]
36
- }
23
+ - **`element`** (Element) - The DOM element for which to generate a unique selector
24
+ - **`options`** (Object, optional) - Configuration options for selector generation
37
25
 
38
- unique( DomElement, options ); // #test
39
- ```
26
+ #### Options
40
27
 
41
- e.g.2 DomElement = `<span test="2"></span>`
28
+ - **`selectorTypes`** (String[], optional) - Array of selector types to use in order of preference. Default: `['id', 'name', 'class', 'tag', 'nth-child']`
29
+
30
+ Available selector types:
31
+ - `'id'` - Uses element's ID attribute (e.g., `#myId`)
32
+ - `'name'` - Uses element's name attribute (e.g., `[name="myName"]`)
33
+ - `'class'` - Uses element's class attributes (e.g., `.myClass`)
34
+ - `'tag'` - Uses element's tag name (e.g., `div`, `span`)
35
+ - `'nth-child'` - Uses nth-child position (e.g., `:nth-child(1)`)
36
+ - `'attributes'` - Uses all attributes except those in `attributesToIgnore`
37
+ - `'data-*'` - Uses specific data attributes (e.g., `data-foo`, `data-test-id`)
38
+ - `'attribute:*'` - Uses specific attributes (e.g., `attribute:role`, `attribute:aria-label`)
39
+
40
+ - **`attributesToIgnore`** (String[], optional) - Array of attribute names to ignore when generating selectors. Default: `['id', 'class', 'length']`
41
+
42
+ - **`filter`** (Function, optional) - A filter function to conditionally reject various traits when building selectors.
43
+
44
+ ```javascript
45
+ function filter(type, key, value) {
46
+ // type: 'attribute', 'class', 'tag', 'nth-child'
47
+ // key: attribute name (for attributes), class name (for classes), etc.
48
+ // value: attribute value, class name, tag name, nth-child position
49
+
50
+ // Return true to allow this trait, false to reject it
51
+ return true;
52
+ }
53
+ ```
42
54
 
43
- ```
44
- import unique from 'unique-selector';
55
+ - **`selectorCache`** (Map<Element, String>, optional) - Cache to improve performance of repeated selector generation. The caller is responsible for cache invalidation.
45
56
 
46
- // Optional Options
47
- options = {
48
- // Array of selector types based on which the unique selector will be generate
49
- selectorTypes : [ 'Attributes' ]
50
- }
57
+ - **`isUniqueCache`** (Map<String, Boolean>, optional) - Cache to improve performance of repeated uniqueness checks. The caller is responsible for cache invalidation.
51
58
 
52
- unique( DomElement, options ); // [test="2"]
53
- ```
59
+ #### Returns
54
60
 
61
+ - **String** - A unique CSS selector for the element, or `null` if no unique selector can be generated
55
62
 
56
- Tests
57
- -----
63
+ ## Usage Examples
58
64
 
59
- $ npm run test
65
+ ### Basic Usage
60
66
 
61
-
62
- Contributing
63
- -----
64
- Feel free to open issues, make suggestions or send PRs.
65
- This project adheres to the Contributor Covenant [code of conduct](http://contributor-covenant.org/). By participating, you are expected to uphold this code.
67
+ ```javascript
68
+ import unique from '@cypress/unique-selector';
66
69
 
70
+ // Get a unique selector for an element
71
+ const element = document.querySelector('.my-button');
72
+ const selector = unique(element);
73
+ // Returns: '#my-button' or '.my-button' or 'button' etc.
74
+ ```
67
75
 
68
- Contact
69
- -----
76
+ ### Custom Selector Types
70
77
 
71
- Avraam Mavridis : [@avraamakis](https://twitter.com/avraamakis)
78
+ ```javascript
79
+ // Use only ID and class selectors
80
+ const selector = unique(element, {
81
+ selectorTypes: ['id', 'class']
82
+ });
72
83
 
73
- Eric Clemmons : [@ericclemmons](https://twitter.com/ericclemmons)
84
+ // Use only data attributes
85
+ const selector = unique(element, {
86
+ selectorTypes: ['data-test-id', 'data-cy']
87
+ });
74
88
 
89
+ // Use specific attributes
90
+ const selector = unique(element, {
91
+ selectorTypes: ['attribute:role', 'attribute:aria-label']
92
+ });
93
+ ```
75
94
 
76
- Releases
77
- --------
95
+ ### Filtering Selectors
96
+
97
+ ```javascript
98
+ // Filter out certain IDs
99
+ const selector = unique(element, {
100
+ filter: (type, key, value) => {
101
+ if (type === 'attribute' && key === 'id') {
102
+ // Only allow IDs that contain 'test'
103
+ return value.includes('test');
104
+ }
105
+ return true;
106
+ }
107
+ });
108
+
109
+ // Filter out certain classes
110
+ const selector = unique(element, {
111
+ filter: (type, key, value) => {
112
+ if (type === 'class') {
113
+ // Only allow classes that start with 'btn-'
114
+ return value.startsWith('btn-');
115
+ }
116
+ return true;
117
+ }
118
+ });
119
+ ```
78
120
 
79
- - v2.1.0
80
-
81
- -
121
+ ### Performance Optimization with Caching
82
122
 
83
- - v0.1.0
123
+ ```javascript
124
+ const selectorCache = new Map();
125
+ const isUniqueCache = new Map();
84
126
 
85
- - Big refactor/rewrite using es6
86
- - More test + Change the test script
87
- - Script to compile es6 to es5 using babel
88
- - Ability to pass options for the **selectorTypes** based on which the unique selector will be generated, see the options
127
+ const selector = unique(element, {
128
+ selectorCache,
129
+ isUniqueCache
130
+ });
89
131
 
132
+ // Reuse the same caches for multiple calls
133
+ const selector2 = unique(anotherElement, {
134
+ selectorCache,
135
+ isUniqueCache
136
+ });
137
+ ```
90
138
 
91
- - v0.0.4
139
+ ### Shadow DOM Support
92
140
 
93
- - Handle extra whitespace in `className` ([#9](https://github.com/ericclemmons/unique-selector/pull/9))
141
+ ```javascript
142
+ // Works with Shadow DOM elements
143
+ const shadowHost = document.querySelector('#shadow-host');
144
+ const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
145
+ const shadowElement = shadowRoot.querySelector('.shadow-button');
94
146
 
95
- - v0.0.3
147
+ const selector = unique(shadowElement);
148
+ // Returns a selector unique within the shadow root context
149
+ ```
96
150
 
97
- - Add support for various `<form>` elements ([#2](https://github.com/ericclemmons/unique-selector/issues/2))
151
+ ## Selector Generation Strategy
98
152
 
99
- - v0.0.2
153
+ The library follows this strategy to generate unique selectors:
100
154
 
101
- - Fix bug with `nth-child` calculation
155
+ 1. **Element-level uniqueness**: First tries to find a selector that uniquely identifies the element within its parent
156
+ 2. **Parent traversal**: If element-level uniqueness fails, traverses up the DOM tree, building a path of selectors
157
+ 3. **Selector type precedence**: Uses the `selectorTypes` array to determine which selector types to try first
158
+ 4. **Combination fallback**: For classes and attributes, tries combinations of multiple selectors if single selectors aren't unique
159
+ 5. **Nth-child fallback**: Uses nth-child positioning as a last resort
102
160
 
103
- - v0.0.1
161
+ ## Examples of Generated Selectors
104
162
 
105
- - Initial release
163
+ ```html
164
+ <!-- Element with ID -->
165
+ <div id="header">Header</div>
166
+ <!-- Selector: #header -->
106
167
 
168
+ <!-- Element with unique class -->
169
+ <button class="submit-btn">Submit</button>
170
+ <!-- Selector: .submit-btn -->
107
171
 
172
+ <!-- Element with multiple classes -->
173
+ <div class="card primary large">Card</div>
174
+ <!-- Selector: .card.primary.large -->
108
175
 
109
- License
110
- -------
176
+ <!-- Element with data attributes -->
177
+ <div data-test-id="user-profile">Profile</div>
178
+ <!-- Selector: [data-test-id="user-profile"] -->
111
179
 
112
- MIT
180
+ <!-- Element with custom attributes -->
181
+ <button role="button" aria-label="Close">×</button>
182
+ <!-- Selector: [role="button"] or [aria-label="Close"] -->
113
183
 
184
+ <!-- Element requiring nth-child -->
185
+ <div class="item">Item 1</div>
186
+ <div class="item">Item 2</div>
187
+ <!-- Selector: :nth-child(1) or :nth-child(2) -->
114
188
 
115
- [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/AvraamMavridis/unique-selector/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
189
+ <!-- Complex nested element -->
190
+ <div class="container">
191
+ <div class="wrapper">
192
+ <span class="text">Hello</span>
193
+ </div>
194
+ </div>
195
+ <!-- Selector: .container > .wrapper > .text -->
196
+ ```
package/lib/index.js CHANGED
@@ -25,6 +25,8 @@ var _getParents = require('./getParents');
25
25
 
26
26
  var _getAttribute = require('./getAttribute');
27
27
 
28
+ var _isShadowRoot = require('./isShadowRoot');
29
+
28
30
  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
31
  * Expose `unique`
30
32
  */
@@ -297,6 +299,19 @@ function unique(el) {
297
299
  if (isUniqueCache) {
298
300
  isUniqueCache.set(maybeUniqueSelector, isUniqueSelector);
299
301
  }
302
+
303
+ // If the selector is not unique but we detect we've reached a ShadowRoot attempt
304
+ // to prefix the selector with `:host >` to anchor against the root. This helps
305
+ // address the possibility of a unique path segment not existing before reaching
306
+ // the root.
307
+ if (!isUniqueSelector && el.parentNode && (0, _isShadowRoot.isShadowRoot)(el.parentNode)) {
308
+ maybeUniqueSelector = ':host > ' + maybeUniqueSelector;
309
+ isUniqueSelector = (0, _isUnique.isUnique)(el, maybeUniqueSelector);
310
+
311
+ if (isUniqueCache) {
312
+ isUniqueCache.set(maybeUniqueSelector, isUniqueSelector);
313
+ }
314
+ }
300
315
  }
301
316
 
302
317
  if (isUniqueSelector) {
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isShadowRoot = isShadowRoot;
7
+ /**
8
+ * Checks if the supplied node is a ShadowRoot
9
+ *
10
+ * @param { Object } element
11
+ * @return { Boolean }
12
+ */
13
+ function isShadowRoot(node) {
14
+ return Object.getPrototypeOf(node).constructor.name === 'ShadowRoot';
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cypress/unique-selector",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
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",
@@ -39,12 +39,11 @@
39
39
  "babel-register": "^6.26.0",
40
40
  "chai": "^4.3.4",
41
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",
42
+ "eslint": "^8.57.1",
43
+ "eslint-config-airbnb": "^19.0.0",
44
+ "eslint-plugin-react": "^7.37.5",
46
45
  "jquery": "^3.6.0",
47
- "jsdom": "^18.0.0",
46
+ "jsdom": "^27.4.0",
48
47
  "mocha": "~9.1.3",
49
48
  "mocha-jsdom": "^2.0.0",
50
49
  "mocha-phantomjs": "~4.1.0",