Skip to content

Commit 6038fb0

Browse files
authored
Merge pull request #32 from Georgegriff/spaces-attr
Spaces attr
2 parents af78f0e + ff4f956 commit 6038fb0

File tree

4 files changed

+195
-10
lines changed

4 files changed

+195
-10
lines changed

README.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@ Both of the methods above accept a 2nd parameter, see section `Provide alternati
2020

2121
### CodeceptJS
2222

23-
2423
More details here: https://github.com/Georgegriff/query-selector-shadow-dom/blob/master/plugins/codeceptjs
2524

25+
### Puppeteer
26+
27+
There are some puppeteer examples available in the examples folder of this repository.
28+
29+
[Puppeteer examples](https://github.com/Georgegriff/query-selector-shadow-dom/blob/master/examples/puppeteer)
30+
2631
### Playwright
2732

2833
Update: as of Playwright v0.14.0 their CSS and text selectors work with shadow Dom out of the box, you don't need this library anymore for Playwright.
@@ -44,13 +49,6 @@ const playwright = require('playwright');
4449

4550
For a full example see: https://github.com/Georgegriff/query-selector-shadow-dom/blob/master/examples/playwright
4651

47-
### Puppeteer
48-
49-
There are some puppeteer examples available in the examples folder of this repository.
50-
51-
[Puppeteer examples](https://github.com/Georgegriff/query-selector-shadow-dom/blob/master/examples/puppeteer)
52-
53-
5452
### Provide alternative node
5553
```javascript
5654
// query from another node

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "query-selector-shadow-dom",
3-
"version": "0.4.6",
3+
"version": "0.5.0",
44
"description": "use querySelector syntax to search for nodes inside of (nested) shadow roots",
55
"main": "src/querySelectorDeep.js",
66
"scripts": {

src/querySelectorDeep.js

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export function querySelectorDeep(selector, root = document) {
2626
}
2727

2828
function _querySelectorDeep(selector, findMany, root) {
29+
selector = normalizeSelector(selector)
2930
let lightElement = root.querySelector(selector);
3031

3132
if (document.head.createShadowRoot || document.head.attachShadow) {
@@ -150,4 +151,162 @@ function collectAllElementsDeep(selector = null, root) {
150151
findAllElements(root.querySelectorAll('*'));
151152

152153
return selector ? allElements.filter(el => el.matches(selector)) : allElements;
153-
}
154+
}
155+
156+
157+
// normalize-selector-rev-02.js
158+
/*
159+
author: kyle simpson (@getify)
160+
original source: https://gist.github.com/getify/9679380
161+
162+
modified for tests by david kaye (@dfkaye)
163+
21 march 2014
164+
165+
rev-02 incorporate kyle's changes 3/2/42014
166+
*/
167+
/* istanbul ignore next */
168+
function normalizeSelector(sel) {
169+
170+
// save unmatched text, if any
171+
function saveUnmatched() {
172+
if (unmatched) {
173+
// whitespace needed after combinator?
174+
if (tokens.length > 0 &&
175+
/^[~+>]$/.test(tokens[tokens.length-1])
176+
) {
177+
tokens.push(" ");
178+
}
179+
180+
// save unmatched text
181+
tokens.push(unmatched);
182+
}
183+
}
184+
185+
var tokens = [], match, unmatched, regex, state = [0],
186+
next_match_idx = 0, prev_match_idx,
187+
not_escaped_pattern = /(?:[^\\]|(?:^|[^\\])(?:\\\\)+)$/,
188+
whitespace_pattern = /^\s+$/,
189+
state_patterns = [
190+
/\s+|\/\*|["'>~+\[\(]/g, // general
191+
/\s+|\/\*|["'\[\]\(\)]/g, // [..] set
192+
/\s+|\/\*|["'\[\]\(\)]/g, // (..) set
193+
null, // string literal (placeholder)
194+
/\*\//g // comment
195+
]
196+
;
197+
198+
sel = sel.trim();
199+
200+
while (true) {
201+
unmatched = "";
202+
203+
regex = state_patterns[state[state.length-1]];
204+
205+
regex.lastIndex = next_match_idx;
206+
match = regex.exec(sel);
207+
208+
// matched text to process?
209+
if (match) {
210+
prev_match_idx = next_match_idx;
211+
next_match_idx = regex.lastIndex;
212+
213+
// collect the previous string chunk not matched before this token
214+
if (prev_match_idx < next_match_idx - match[0].length) {
215+
unmatched = sel.substring(prev_match_idx,next_match_idx - match[0].length);
216+
}
217+
218+
// general, [ ] pair, ( ) pair?
219+
if (state[state.length-1] < 3) {
220+
saveUnmatched();
221+
222+
// starting a [ ] pair?
223+
if (match[0] === "[") {
224+
state.push(1);
225+
}
226+
// starting a ( ) pair?
227+
else if (match[0] === "(") {
228+
state.push(2);
229+
}
230+
// starting a string literal?
231+
else if (/^["']$/.test(match[0])) {
232+
state.push(3);
233+
state_patterns[3] = new RegExp(match[0],"g");
234+
}
235+
// starting a comment?
236+
else if (match[0] === "/*") {
237+
state.push(4);
238+
}
239+
// ending a [ ] or ( ) pair?
240+
else if (/^[\]\)]$/.test(match[0]) && state.length > 0) {
241+
state.pop();
242+
}
243+
// handling whitespace or a combinator?
244+
else if (/^(?:\s+|[~+>])$/.test(match[0])) {
245+
246+
// need to insert whitespace before?
247+
if (tokens.length > 0 &&
248+
!whitespace_pattern.test(tokens[tokens.length-1]) &&
249+
state[state.length-1] === 0
250+
) {
251+
// add normalized whitespace
252+
tokens.push(" ");
253+
}
254+
255+
// case-insensitive attribute selector CSS L4
256+
if (state[state.length-1] === 1 &&
257+
tokens.length === 5 &&
258+
tokens[2].charAt(tokens[2].length-1) === '=') {
259+
tokens[4] = " " + tokens[4];
260+
}
261+
262+
// whitespace token we can skip?
263+
if (whitespace_pattern.test(match[0])) {
264+
continue;
265+
}
266+
}
267+
268+
// save matched text
269+
tokens.push(match[0]);
270+
}
271+
// otherwise, string literal or comment
272+
else {
273+
// save unmatched text
274+
tokens[tokens.length-1] += unmatched;
275+
276+
// unescaped terminator to string literal or comment?
277+
if (not_escaped_pattern.test(tokens[tokens.length-1])) {
278+
// comment terminator?
279+
if (state[state.length-1] === 4) {
280+
// ok to drop comment?
281+
if (tokens.length < 2 ||
282+
whitespace_pattern.test(tokens[tokens.length-2])
283+
) {
284+
tokens.pop();
285+
}
286+
// otherwise, turn comment into whitespace
287+
else {
288+
tokens[tokens.length-1] = " ";
289+
}
290+
291+
// handled already
292+
match[0] = "";
293+
}
294+
295+
state.pop();
296+
}
297+
298+
// append matched text to existing token
299+
tokens[tokens.length-1] += match[0];
300+
}
301+
}
302+
// otherwise, end of processing (no more matches)
303+
else {
304+
unmatched = sel.substr(next_match_idx);
305+
saveUnmatched();
306+
307+
break;
308+
}
309+
}
310+
311+
return tokens.join("").trim();
312+
}

test/basic.spec.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,34 @@ describe("Basic Suite", function() {
198198
expect(testComponents.length).toEqual(5);
199199
});
200200

201+
it('can handle spacing around attribute values', function() {
202+
const testComponent = createTestComponent(parent, {
203+
childClassName: 'header-1',
204+
internalHTML: '<div class="header-2">Content</div>'
205+
});
206+
const test2 = createTestComponent(testComponent, {
207+
childClassName: 'header-2'
208+
});
209+
test2.setAttribute('data-test', 'Hello, World')
210+
testComponent.classList.add('header-1');
211+
const testComponents = querySelectorAllDeep(`.header-1 [ data-test = "Hello, World" ], .header-2, .header-1`);
212+
expect(testComponents.length).toEqual(5);
213+
});
214+
215+
it('can handle spacing around attribute values with [ in attribute', function() {
216+
const testComponent = createTestComponent(parent, {
217+
childClassName: 'header-1',
218+
internalHTML: '<div class="header-2">Content</div>'
219+
});
220+
const test2 = createTestComponent(testComponent, {
221+
childClassName: 'header-2'
222+
});
223+
test2.setAttribute('data-braces-test', ' [ Hello, World ] ')
224+
testComponent.classList.add('header-1');
225+
const testComponents = querySelectorAllDeep(`.header-1 [ data-braces-test = " [ Hello, World ] " ], .header-2, .header-1`);
226+
expect(testComponents.length).toEqual(5);
227+
});
228+
201229
it('can escaped comma in attribute values', function() {
202230
const testComponent = createTestComponent(parent, {
203231
childClassName: 'header-1',

0 commit comments

Comments
 (0)