Skip to content

Commit

Permalink
Merge pull request #41 from Georgegriff/v_0_8_0
Browse files Browse the repository at this point in the history
pass at coverage for new 0.8.0 feats
  • Loading branch information
Georgegriff authored Oct 9, 2020
2 parents 69a5db2 + 1fe0b6a commit 98fd91a
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 158 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "query-selector-shadow-dom",
"version": "0.7.1",
"version": "0.8.0",
"description": "use querySelector syntax to search for nodes inside of (nested) shadow roots",
"main": "src/querySelectorDeep.js",
"scripts": {
Expand Down
165 changes: 165 additions & 0 deletions src/normalize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/* istanbul ignore file */


// normalize-selector-rev-02.js
/*
author: kyle simpson (@getify)
original source: https://gist.github.com/getify/9679380
modified for tests by david kaye (@dfkaye)
21 march 2014
rev-02 incorporate kyle's changes 3/2/42014
*/

export function normalizeSelector(sel) {
// save unmatched text, if any
function saveUnmatched() {
if (unmatched) {
// whitespace needed after combinator?
if (tokens.length > 0 && /^[~+>]$/.test(tokens[tokens.length - 1])) {
tokens.push(" ");
}

// save unmatched text
tokens.push(unmatched);
}
}

var tokens = [],
match,
unmatched,
regex,
state = [0],
next_match_idx = 0,
prev_match_idx,
not_escaped_pattern = /(?:[^\\]|(?:^|[^\\])(?:\\\\)+)$/,
whitespace_pattern = /^\s+$/,
state_patterns = [
/\s+|\/\*|["'>~+\[\(]/g, // general
/\s+|\/\*|["'\[\]\(\)]/g, // [..] set
/\s+|\/\*|["'\[\]\(\)]/g, // (..) set
null, // string literal (placeholder)
/\*\//g, // comment
];
sel = sel.trim();

while (true) {
unmatched = "";

regex = state_patterns[state[state.length - 1]];

regex.lastIndex = next_match_idx;
match = regex.exec(sel);

// matched text to process?
if (match) {
prev_match_idx = next_match_idx;
next_match_idx = regex.lastIndex;

// collect the previous string chunk not matched before this token
if (prev_match_idx < next_match_idx - match[0].length) {
unmatched = sel.substring(
prev_match_idx,
next_match_idx - match[0].length
);
}

// general, [ ] pair, ( ) pair?
if (state[state.length - 1] < 3) {
saveUnmatched();

// starting a [ ] pair?
if (match[0] === "[") {
state.push(1);
}
// starting a ( ) pair?
else if (match[0] === "(") {
state.push(2);
}
// starting a string literal?
else if (/^["']$/.test(match[0])) {
state.push(3);
state_patterns[3] = new RegExp(match[0], "g");
}
// starting a comment?
else if (match[0] === "/*") {
state.push(4);
}
// ending a [ ] or ( ) pair?
else if (/^[\]\)]$/.test(match[0]) && state.length > 0) {
state.pop();
}
// handling whitespace or a combinator?
else if (/^(?:\s+|[~+>])$/.test(match[0])) {
// need to insert whitespace before?
if (
tokens.length > 0 &&
!whitespace_pattern.test(tokens[tokens.length - 1]) &&
state[state.length - 1] === 0
) {
// add normalized whitespace
tokens.push(" ");
}

// case-insensitive attribute selector CSS L4
if (
state[state.length - 1] === 1 &&
tokens.length === 5 &&
tokens[2].charAt(tokens[2].length - 1) === "="
) {
tokens[4] = " " + tokens[4];
}

// whitespace token we can skip?
if (whitespace_pattern.test(match[0])) {
continue;
}
}

// save matched text
tokens.push(match[0]);
}
// otherwise, string literal or comment
else {
// save unmatched text
tokens[tokens.length - 1] += unmatched;

// unescaped terminator to string literal or comment?
if (not_escaped_pattern.test(tokens[tokens.length - 1])) {
// comment terminator?
if (state[state.length - 1] === 4) {
// ok to drop comment?
if (
tokens.length < 2 ||
whitespace_pattern.test(tokens[tokens.length - 2])
) {
tokens.pop();
}
// otherwise, turn comment into whitespace
else {
tokens[tokens.length - 1] = " ";
}

// handled already
match[0] = "";
}

state.pop();
}

// append matched text to existing token
tokens[tokens.length - 1] += match[0];
}
}
// otherwise, end of processing (no more matches)
else {
unmatched = sel.substr(next_match_idx);
saveUnmatched();

break;
}
}

return tokens.join("").trim();
}
158 changes: 2 additions & 156 deletions src/querySelectorDeep.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* License Apache-2.0
*/

import { normalizeSelector } from './normalize';

/**
* Finds first matching elements on the page that may be in a shadow root using a complex selector of n-depth
*
Expand Down Expand Up @@ -177,159 +179,3 @@ export function collectAllElementsDeep(selector = null, root, cachedElements = n
return allElements.filter(el => el.matches(selector));
}

// normalize-selector-rev-02.js
/*
author: kyle simpson (@getify)
original source: https://gist.github.com/getify/9679380
modified for tests by david kaye (@dfkaye)
21 march 2014
rev-02 incorporate kyle's changes 3/2/42014
*/
/* istanbul ignore next */
function normalizeSelector(sel) {

// save unmatched text, if any
function saveUnmatched() {
if (unmatched) {
// whitespace needed after combinator?
if (tokens.length > 0 &&
/^[~+>]$/.test(tokens[tokens.length-1])
) {
tokens.push(" ");
}

// save unmatched text
tokens.push(unmatched);
}
}

var tokens = [], match, unmatched, regex, state = [0],
next_match_idx = 0, prev_match_idx,
not_escaped_pattern = /(?:[^\\]|(?:^|[^\\])(?:\\\\)+)$/,
whitespace_pattern = /^\s+$/,
state_patterns = [
/\s+|\/\*|["'>~+\[\(]/g, // general
/\s+|\/\*|["'\[\]\(\)]/g, // [..] set
/\s+|\/\*|["'\[\]\(\)]/g, // (..) set
null, // string literal (placeholder)
/\*\//g // comment
]
;

sel = sel.trim();

while (true) {
unmatched = "";

regex = state_patterns[state[state.length-1]];

regex.lastIndex = next_match_idx;
match = regex.exec(sel);

// matched text to process?
if (match) {
prev_match_idx = next_match_idx;
next_match_idx = regex.lastIndex;

// collect the previous string chunk not matched before this token
if (prev_match_idx < next_match_idx - match[0].length) {
unmatched = sel.substring(prev_match_idx,next_match_idx - match[0].length);
}

// general, [ ] pair, ( ) pair?
if (state[state.length-1] < 3) {
saveUnmatched();

// starting a [ ] pair?
if (match[0] === "[") {
state.push(1);
}
// starting a ( ) pair?
else if (match[0] === "(") {
state.push(2);
}
// starting a string literal?
else if (/^["']$/.test(match[0])) {
state.push(3);
state_patterns[3] = new RegExp(match[0],"g");
}
// starting a comment?
else if (match[0] === "/*") {
state.push(4);
}
// ending a [ ] or ( ) pair?
else if (/^[\]\)]$/.test(match[0]) && state.length > 0) {
state.pop();
}
// handling whitespace or a combinator?
else if (/^(?:\s+|[~+>])$/.test(match[0])) {

// need to insert whitespace before?
if (tokens.length > 0 &&
!whitespace_pattern.test(tokens[tokens.length-1]) &&
state[state.length-1] === 0
) {
// add normalized whitespace
tokens.push(" ");
}

// case-insensitive attribute selector CSS L4
if (state[state.length-1] === 1 &&
tokens.length === 5 &&
tokens[2].charAt(tokens[2].length-1) === '=') {
tokens[4] = " " + tokens[4];
}

// whitespace token we can skip?
if (whitespace_pattern.test(match[0])) {
continue;
}
}

// save matched text
tokens.push(match[0]);
}
// otherwise, string literal or comment
else {
// save unmatched text
tokens[tokens.length-1] += unmatched;

// unescaped terminator to string literal or comment?
if (not_escaped_pattern.test(tokens[tokens.length-1])) {
// comment terminator?
if (state[state.length-1] === 4) {
// ok to drop comment?
if (tokens.length < 2 ||
whitespace_pattern.test(tokens[tokens.length-2])
) {
tokens.pop();
}
// otherwise, turn comment into whitespace
else {
tokens[tokens.length-1] = " ";
}

// handled already
match[0] = "";
}

state.pop();
}

// append matched text to existing token
tokens[tokens.length-1] += match[0];
}
}
// otherwise, end of processing (no more matches)
else {
unmatched = sel.substr(next_match_idx);
saveUnmatched();

break;
}
}

return tokens.join("").trim();
}
25 changes: 24 additions & 1 deletion test/basic.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,29 @@ describe("Basic Suite", function() {
expect(testComponents[0].classList.contains('find-me')).toEqual(true);
});

it('handles descendant selector > that dooes not match child', function() {
const testComponent = createTestComponent(parent, {
childClassName: 'header-1',
internalHTML: '<div class="header-2"><div class="find-me"></div></div>'
});
testComponent.shadowRoot.querySelector('.header-2').host = "test.com";
testComponent.classList.add('container');
const testComponents = querySelectorAllDeep(`.container > div > .header-2 > .doesnt-exist`);
expect(testComponents.length).toEqual(0);
});

it('handles descendant selector where child exists but parent does not', function() {
const testComponent = createTestComponent(parent, {
childClassName: 'header-1',
internalHTML: '<div class="header-2"><div class="find-me"></div></div>'
});
testComponent.shadowRoot.querySelector('.header-2').host = "test.com";
testComponent.classList.add('container');
const testComponents = querySelectorAllDeep(`.container > div > .doesnt-exist > .find-me`);
expect(testComponents.length).toEqual(0);
});


it('can handle extra white space in selectors', function() {
const testComponent = createTestComponent(parent, {
childClassName: 'header-1',
Expand Down Expand Up @@ -340,7 +363,7 @@ describe("Basic Suite", function() {
createTestComponent(parent, {
childClassName: 'inner-content'
});
const collectedElements = collectAllElementsDeep('', root)
const collectedElements = collectAllElementsDeep('*', root)
expect(collectedElements.length).toEqual(4);

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

0 comments on commit 98fd91a

Please sign in to comment.