diff --git a/README.md b/README.md index f7c45b6..1ad63b9 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,14 @@ Both of the methods above accept a 2nd parameter, see section `Provide alternati ### CodeceptJS - More details here: https://github.com/Georgegriff/query-selector-shadow-dom/blob/master/plugins/codeceptjs +### Puppeteer + +There are some puppeteer examples available in the examples folder of this repository. + +[Puppeteer examples](https://github.com/Georgegriff/query-selector-shadow-dom/blob/master/examples/puppeteer) + ### Playwright 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'); For a full example see: https://github.com/Georgegriff/query-selector-shadow-dom/blob/master/examples/playwright -### Puppeteer - -There are some puppeteer examples available in the examples folder of this repository. - -[Puppeteer examples](https://github.com/Georgegriff/query-selector-shadow-dom/blob/master/examples/puppeteer) - - ### Provide alternative node ```javascript // query from another node diff --git a/package.json b/package.json index f325e2f..adff070 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "query-selector-shadow-dom", - "version": "0.4.6", + "version": "0.5.0", "description": "use querySelector syntax to search for nodes inside of (nested) shadow roots", "main": "src/querySelectorDeep.js", "scripts": { diff --git a/src/querySelectorDeep.js b/src/querySelectorDeep.js index d859925..43b4559 100644 --- a/src/querySelectorDeep.js +++ b/src/querySelectorDeep.js @@ -26,6 +26,7 @@ export function querySelectorDeep(selector, root = document) { } function _querySelectorDeep(selector, findMany, root) { + selector = normalizeSelector(selector) let lightElement = root.querySelector(selector); if (document.head.createShadowRoot || document.head.attachShadow) { @@ -150,4 +151,162 @@ function collectAllElementsDeep(selector = null, root) { findAllElements(root.querySelectorAll('*')); return selector ? allElements.filter(el => el.matches(selector)) : allElements; -} \ No newline at end of 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 +*/ + /* 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(); + } \ No newline at end of file diff --git a/test/basic.spec.js b/test/basic.spec.js index 0dd21f5..6b4f2f7 100644 --- a/test/basic.spec.js +++ b/test/basic.spec.js @@ -198,6 +198,34 @@ describe("Basic Suite", function() { expect(testComponents.length).toEqual(5); }); + it('can handle spacing around attribute values', function() { + const testComponent = createTestComponent(parent, { + childClassName: 'header-1', + internalHTML: '