Skip to content

Commit 1fe0b6a

Browse files
author
George Griffiths
committed
pass at coverage for new 0.8.0 feats
1 parent 69a5db2 commit 1fe0b6a

File tree

4 files changed

+192
-158
lines changed

4 files changed

+192
-158
lines changed

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.7.1",
3+
"version": "0.8.0",
44
"description": "use querySelector syntax to search for nodes inside of (nested) shadow roots",
55
"main": "src/querySelectorDeep.js",
66
"scripts": {

src/normalize.js

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/* istanbul ignore file */
2+
3+
4+
// normalize-selector-rev-02.js
5+
/*
6+
author: kyle simpson (@getify)
7+
original source: https://gist.github.com/getify/9679380
8+
9+
modified for tests by david kaye (@dfkaye)
10+
21 march 2014
11+
12+
rev-02 incorporate kyle's changes 3/2/42014
13+
*/
14+
15+
export function normalizeSelector(sel) {
16+
// save unmatched text, if any
17+
function saveUnmatched() {
18+
if (unmatched) {
19+
// whitespace needed after combinator?
20+
if (tokens.length > 0 && /^[~+>]$/.test(tokens[tokens.length - 1])) {
21+
tokens.push(" ");
22+
}
23+
24+
// save unmatched text
25+
tokens.push(unmatched);
26+
}
27+
}
28+
29+
var tokens = [],
30+
match,
31+
unmatched,
32+
regex,
33+
state = [0],
34+
next_match_idx = 0,
35+
prev_match_idx,
36+
not_escaped_pattern = /(?:[^\\]|(?:^|[^\\])(?:\\\\)+)$/,
37+
whitespace_pattern = /^\s+$/,
38+
state_patterns = [
39+
/\s+|\/\*|["'>~+\[\(]/g, // general
40+
/\s+|\/\*|["'\[\]\(\)]/g, // [..] set
41+
/\s+|\/\*|["'\[\]\(\)]/g, // (..) set
42+
null, // string literal (placeholder)
43+
/\*\//g, // comment
44+
];
45+
sel = sel.trim();
46+
47+
while (true) {
48+
unmatched = "";
49+
50+
regex = state_patterns[state[state.length - 1]];
51+
52+
regex.lastIndex = next_match_idx;
53+
match = regex.exec(sel);
54+
55+
// matched text to process?
56+
if (match) {
57+
prev_match_idx = next_match_idx;
58+
next_match_idx = regex.lastIndex;
59+
60+
// collect the previous string chunk not matched before this token
61+
if (prev_match_idx < next_match_idx - match[0].length) {
62+
unmatched = sel.substring(
63+
prev_match_idx,
64+
next_match_idx - match[0].length
65+
);
66+
}
67+
68+
// general, [ ] pair, ( ) pair?
69+
if (state[state.length - 1] < 3) {
70+
saveUnmatched();
71+
72+
// starting a [ ] pair?
73+
if (match[0] === "[") {
74+
state.push(1);
75+
}
76+
// starting a ( ) pair?
77+
else if (match[0] === "(") {
78+
state.push(2);
79+
}
80+
// starting a string literal?
81+
else if (/^["']$/.test(match[0])) {
82+
state.push(3);
83+
state_patterns[3] = new RegExp(match[0], "g");
84+
}
85+
// starting a comment?
86+
else if (match[0] === "/*") {
87+
state.push(4);
88+
}
89+
// ending a [ ] or ( ) pair?
90+
else if (/^[\]\)]$/.test(match[0]) && state.length > 0) {
91+
state.pop();
92+
}
93+
// handling whitespace or a combinator?
94+
else if (/^(?:\s+|[~+>])$/.test(match[0])) {
95+
// need to insert whitespace before?
96+
if (
97+
tokens.length > 0 &&
98+
!whitespace_pattern.test(tokens[tokens.length - 1]) &&
99+
state[state.length - 1] === 0
100+
) {
101+
// add normalized whitespace
102+
tokens.push(" ");
103+
}
104+
105+
// case-insensitive attribute selector CSS L4
106+
if (
107+
state[state.length - 1] === 1 &&
108+
tokens.length === 5 &&
109+
tokens[2].charAt(tokens[2].length - 1) === "="
110+
) {
111+
tokens[4] = " " + tokens[4];
112+
}
113+
114+
// whitespace token we can skip?
115+
if (whitespace_pattern.test(match[0])) {
116+
continue;
117+
}
118+
}
119+
120+
// save matched text
121+
tokens.push(match[0]);
122+
}
123+
// otherwise, string literal or comment
124+
else {
125+
// save unmatched text
126+
tokens[tokens.length - 1] += unmatched;
127+
128+
// unescaped terminator to string literal or comment?
129+
if (not_escaped_pattern.test(tokens[tokens.length - 1])) {
130+
// comment terminator?
131+
if (state[state.length - 1] === 4) {
132+
// ok to drop comment?
133+
if (
134+
tokens.length < 2 ||
135+
whitespace_pattern.test(tokens[tokens.length - 2])
136+
) {
137+
tokens.pop();
138+
}
139+
// otherwise, turn comment into whitespace
140+
else {
141+
tokens[tokens.length - 1] = " ";
142+
}
143+
144+
// handled already
145+
match[0] = "";
146+
}
147+
148+
state.pop();
149+
}
150+
151+
// append matched text to existing token
152+
tokens[tokens.length - 1] += match[0];
153+
}
154+
}
155+
// otherwise, end of processing (no more matches)
156+
else {
157+
unmatched = sel.substr(next_match_idx);
158+
saveUnmatched();
159+
160+
break;
161+
}
162+
}
163+
164+
return tokens.join("").trim();
165+
}

src/querySelectorDeep.js

Lines changed: 2 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* License Apache-2.0
44
*/
55

6+
import { normalizeSelector } from './normalize';
7+
68
/**
79
* Finds first matching elements on the page that may be in a shadow root using a complex selector of n-depth
810
*
@@ -177,159 +179,3 @@ export function collectAllElementsDeep(selector = null, root, cachedElements = n
177179
return allElements.filter(el => el.matches(selector));
178180
}
179181

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

test/basic.spec.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,29 @@ describe("Basic Suite", function() {
154154
expect(testComponents[0].classList.contains('find-me')).toEqual(true);
155155
});
156156

157+
it('handles descendant selector > that dooes not match child', function() {
158+
const testComponent = createTestComponent(parent, {
159+
childClassName: 'header-1',
160+
internalHTML: '<div class="header-2"><div class="find-me"></div></div>'
161+
});
162+
testComponent.shadowRoot.querySelector('.header-2').host = "test.com";
163+
testComponent.classList.add('container');
164+
const testComponents = querySelectorAllDeep(`.container > div > .header-2 > .doesnt-exist`);
165+
expect(testComponents.length).toEqual(0);
166+
});
167+
168+
it('handles descendant selector where child exists but parent does not', function() {
169+
const testComponent = createTestComponent(parent, {
170+
childClassName: 'header-1',
171+
internalHTML: '<div class="header-2"><div class="find-me"></div></div>'
172+
});
173+
testComponent.shadowRoot.querySelector('.header-2').host = "test.com";
174+
testComponent.classList.add('container');
175+
const testComponents = querySelectorAllDeep(`.container > div > .doesnt-exist > .find-me`);
176+
expect(testComponents.length).toEqual(0);
177+
});
178+
179+
157180
it('can handle extra white space in selectors', function() {
158181
const testComponent = createTestComponent(parent, {
159182
childClassName: 'header-1',
@@ -340,7 +363,7 @@ describe("Basic Suite", function() {
340363
createTestComponent(parent, {
341364
childClassName: 'inner-content'
342365
});
343-
const collectedElements = collectAllElementsDeep('', root)
366+
const collectedElements = collectAllElementsDeep('*', root)
344367
expect(collectedElements.length).toEqual(4);
345368

346369
const testComponents = querySelectorAllDeep('.inner-content', root, collectedElements);

0 commit comments

Comments
 (0)