Skip to content

Commit 513190c

Browse files
committed
fixes #2 and add support for spaces in quoted attributes and escaped quotes
1 parent 7946784 commit 513190c

File tree

4 files changed

+141
-18
lines changed

4 files changed

+141
-18
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "query-selector-shadow-dom",
3-
"version": "0.2.2",
3+
"version": "0.2.3",
44
"description": "use querySelector syntax to search for nodes inside of (nested) shadow roots",
55
"main": "src/querySelectorDeep.js",
66
"scripts": {
@@ -49,4 +49,4 @@
4949
"type": "git",
5050
"url": "https://github.com/Georgegriff/query-selector-shadow-dom"
5151
}
52-
}
52+
}

src/querySelectorDeep.js

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,50 @@ function _querySelectorDeep(selector, findMany) {
3333
if (!findMany && lightElement) {
3434
return lightElement;
3535
}
36-
// do best to support complex selectors and split the query
37-
const splitSelector = selector.replace(/\s*([,>+~]+)\s*/g, '$1').replace(/\s\s+/g, ' ').split(/\s/);
38-
const possibleElementsIndex = splitSelector.length - 1;
39-
const possibleElements = collectAllElementsDeep(splitSelector[possibleElementsIndex]);
40-
const findElements = findMatchingElement(splitSelector, possibleElementsIndex);
41-
if (findMany) {
42-
return possibleElements.filter(findElements);
43-
} else {
44-
return possibleElements.find(findElements);
45-
}
36+
37+
// split on commas because those are a logical divide in the operation
38+
const selectionsToMake = selector.split(/,\s*/);
39+
40+
return selectionsToMake.reduce((acc, minimalSelector) => {
41+
// if not finding many just reduce the first match
42+
if (!findMany && acc) {
43+
return acc;
44+
}
45+
// do best to support complex selectors and split the query
46+
const splitSelector = minimalSelector
47+
//remove white space at start of selector
48+
.replace(/^\s+/g, '')
49+
.replace(/\s*([>+~]+)\s*/g, '$1')
50+
.replace(/\s\s+/g, ' ')
51+
// split on space unless in quotes
52+
.match(/\\?.|^$/g).reduce((p, c) => {
53+
if (c === '"' && !p.sQuote) {
54+
p.quote ^= 1;
55+
p.a[p.a.length - 1] += c;
56+
} else if (c === '\'' && !p.quote) {
57+
p.sQuote ^= 1;
58+
p.a[p.a.length - 1] += c;
59+
60+
} else if (!p.quote && !p.sQuote && c === ' ') {
61+
p.a.push('');
62+
} else {
63+
p.a[p.a.length - 1] += c;
64+
}
65+
return p;
66+
}, { a: [''] }).a
67+
const possibleElementsIndex = splitSelector.length - 1;
68+
const possibleElements = collectAllElementsDeep(splitSelector[possibleElementsIndex]);
69+
const findElements = findMatchingElement(splitSelector, possibleElementsIndex);
70+
if (findMany) {
71+
acc = acc.concat(possibleElements.filter(findElements));
72+
return acc;
73+
} else {
74+
acc = possibleElements.find(findElements);
75+
return acc;
76+
}
77+
}, findMany ? [] : null);
78+
79+
4680
} else {
4781
if (!findMany) {
4882
return lightElement;

test/basic.spec.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,94 @@ describe("Basic Suite", function() {
105105

106106
});
107107

108+
it('can handle attribute selector value', function() {
109+
const testComponent = createTestComponent(parent, {
110+
childClassName: 'header-1',
111+
internalHTML: '<div data-test="Hello-World" class="header-2">Content</div>'
112+
});
113+
createTestComponent(testComponent, {
114+
childClassName: 'header-2'
115+
});
116+
testComponent.setAttribute('data-test', '123')
117+
testComponent.classList.add('header-1');
118+
const testComponents = querySelectorAllDeep(`.header-1 [data-test="Hello-World"]`);
119+
expect(testComponents.length).toEqual(1);
120+
expect(testComponents[0].classList.contains('header-2')).toBeTruthy();
121+
});
122+
123+
124+
it('can handle extra white space in attribute value', function() {
125+
const testComponent = createTestComponent(parent, {
126+
childClassName: 'header-1',
127+
internalHTML: '<div data-test="Hello World" class="header-2">Content</div>'
128+
});
129+
createTestComponent(testComponent, {
130+
childClassName: 'header-2'
131+
});
132+
// this should not match as matching children
133+
testComponent.setAttribute('data-test', 'Hello World')
134+
testComponent.classList.add('header-1');
135+
const testComponents = querySelectorAllDeep(`.header-1 [data-test="Hello World"]`);
136+
expect(testComponents.length).toEqual(1);
137+
});
138+
139+
it('can handle escaped data in attributes', function() {
140+
const testComponent = createTestComponent(parent, {
141+
childClassName: 'header-1',
142+
internalHTML: '<div class="header-2">Content</div>'
143+
});
144+
const test2 = createTestComponent(testComponent, {
145+
childClassName: 'header-2'
146+
});
147+
test2.setAttribute('data-test', 'Hello" World')
148+
testComponent.classList.add('header-1');
149+
const testComponents = querySelectorAllDeep(`.header-1 [data-test="Hello\\" World"]`);
150+
expect(testComponents.length).toEqual(1);
151+
});
152+
153+
it('can handle extra white space in single quoted attribute value', function() {
154+
const testComponent = createTestComponent(parent, {
155+
childClassName: 'header-1',
156+
internalHTML: '<div class="header-2">Content</div>'
157+
});
158+
createTestComponent(testComponent, {
159+
childClassName: 'header-2'
160+
});
161+
testComponent.setAttribute('data-test', 'Hello " \'World\'')
162+
testComponent.classList.add('header-1');
163+
const testComponents = querySelectorAllDeep(`.header-1[data-test='Hello \\" \\'World\\'']`);
164+
expect(testComponents.length).toEqual(1);
165+
});
166+
167+
it('split correctly on selector list', function() {
168+
const testComponent = createTestComponent(parent, {
169+
internalHTML: '<span class="header-2"></span><div data-test="Hello" World" class="header-3">Content</div>'
170+
});
171+
createTestComponent(testComponent, {
172+
childClassName: 'header-4'
173+
});
174+
testComponent.setAttribute('data-test', '123')
175+
testComponent.classList.add('header-1');
176+
const testComponents = querySelectorAllDeep(`.header-1,.header-2 + .header-3`);
177+
expect(testComponents.length).toEqual(2);
178+
expect(testComponents[1].classList.contains('header-3')).toBeTruthy();
179+
});
180+
181+
it('split correctly on selector list (ignore white space)', function() {
182+
const testComponent = createTestComponent(parent, {
183+
internalHTML: '<span class="header-2"></span><div data-test="Hello World" class="header-3">Content</div>'
184+
});
185+
createTestComponent(testComponent, {
186+
childClassName: 'header-4'
187+
});
188+
testComponent.setAttribute('data-test', '123')
189+
testComponent.classList.add('header-1');
190+
const testComponents = querySelectorAllDeep(` .header-1, .header-2 + .header-3`);
191+
expect(testComponents.length).toEqual(2);
192+
expect(testComponents[1].classList.contains('header-3')).toBeTruthy();
193+
});
194+
195+
108196

109197
// describe(".perf", function() {
110198

test/index.html

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,16 @@
3131
createTestComponent(baseComponent, 1, {
3232
childClassName: 'childy'
3333
});
34-
const testComponent = createTestComponent(baseComponent, {
35-
childClassName: 'header-1',
36-
internalHTML: '<div class="header-2">Content</div>'
34+
35+
const testComponent = createTestComponent(parent, {
36+
internalHTML: `<span class="header-2"></span><div data-test="Hello World" class="header-3">Content</div>`
3737
});
38-
testComponent.classList.add('header-1');
3938
createTestComponent(testComponent, {
40-
childClassName: 'header-2'
39+
childClassName: 'header-4'
4140
});
42-
const testComponents = querySelectorAllDeep(`.header-1 .header-2`);
41+
testComponent.setAttribute('data-test', 'Hello " \'World\'')
42+
testComponent.classList.add('header-1');
43+
const testComponents = querySelectorAllDeep(`.header-1[data-test='Hello \\" \\'World\\'']`);
4344
console.log(testComponents)
4445

4546
window.querySelectorAllDeep = querySelectorAllDeep;

0 commit comments

Comments
 (0)