From d7f57549acf3f7c4800c39613705aaa8e23b58c1 Mon Sep 17 00:00:00 2001 From: James Speirs Date: Wed, 29 Oct 2014 15:13:51 -0600 Subject: [PATCH 1/3] Add ability to run scoped queries when the parent node is a document fragment. Improved modified query string to allow multiple selectors per query. Declared local variable that was incorrectly using global space. --- src/scopedQuerySelectorShim.js | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/scopedQuerySelectorShim.js b/src/scopedQuerySelectorShim.js index 357e15c..0f30412 100644 --- a/src/scopedQuerySelectorShim.js +++ b/src/scopedQuerySelectorShim.js @@ -13,8 +13,9 @@ container.querySelectorAll(':scope *'); } catch (e) { + // Match usage of scope - var scopeRE = /^\s*:scope/gi; + var scopeRE = /\s*:scope\s*/gi; // Overrides function overrideNodeMethod(prototype, methodName) { @@ -23,13 +24,12 @@ // Override the method prototype[methodName] = function(query) { - var nodeList, - gaveId = false, - gaveContainer = false; + var nodeList, parentNode, frag, + gaveId = false, + gaveContainer = false, + parentIsFragment = false; if (query.match(scopeRE)) { - // Remove :scope - query = query.replace(scopeRE, ''); if (!this.parentNode) { // Add to temporary container @@ -37,6 +37,12 @@ gaveContainer = true; } + if (this.parentNode instanceof DocumentFragment) { + frag = this.parentNode; + while (frag.firstChild) container.appendChild(frag.firstChild); + parentIsFragment = true; + } + parentNode = this.parentNode; if (!this.id) { @@ -45,8 +51,11 @@ gaveId = true; } + // replace :scope with ID selector + query = query.replace(scopeRE, '#' + this.id + ' '); + // Find elements against parent node - nodeList = oldMethod.call(parentNode, '#'+this.id+' '+query); + nodeList = oldMethod.call(parentNode, query); // Reset the ID if (gaveId) { @@ -54,7 +63,9 @@ } // Remove from temporary container - if (gaveContainer) { + if (parentIsFragment) { + while (container.firstChild) frag.appendChild(container.firstChild); + } else if (gaveContainer) { container.removeChild(this); } @@ -71,4 +82,4 @@ overrideNodeMethod(HTMLElement.prototype, 'querySelector'); overrideNodeMethod(HTMLElement.prototype, 'querySelectorAll'); } -}()); +}()); \ No newline at end of file From 5465f9a14c90fd8eea12635e012e5304d103fbfb Mon Sep 17 00:00:00 2001 From: James Speirs Date: Wed, 29 Oct 2014 15:34:32 -0600 Subject: [PATCH 2/3] Improved regular expressions to avoid false positives. --- src/scopedQuerySelectorShim.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/scopedQuerySelectorShim.js b/src/scopedQuerySelectorShim.js index 0f30412..5083407 100644 --- a/src/scopedQuerySelectorShim.js +++ b/src/scopedQuerySelectorShim.js @@ -13,9 +13,9 @@ container.querySelectorAll(':scope *'); } catch (e) { - - // Match usage of scope - var scopeRE = /\s*:scope\s*/gi; + var rxTest = /(?:^|,)\s*:scope\s+/, + rxStart = /^\s*:scope\s+/i, + rxOthers = /,\s*:scope\s+/gi; // Overrides function overrideNodeMethod(prototype, methodName) { @@ -24,12 +24,12 @@ // Override the method prototype[methodName] = function(query) { - var nodeList, parentNode, frag, + var nodeList, parentNode, frag, idSelector, gaveId = false, gaveContainer = false, parentIsFragment = false; - if (query.match(scopeRE)) { + if (rxTest.test(query)) { if (!this.parentNode) { // Add to temporary container @@ -52,7 +52,8 @@ } // replace :scope with ID selector - query = query.replace(scopeRE, '#' + this.id + ' '); + idSelector = '#' + this.id + ' '; + query = query.replace(rxStart, idSelector).replace(rxOthers, ', ' + idSelector); // Find elements against parent node nodeList = oldMethod.call(parentNode, query); From ceb7f5853caf8d3268aadcc6d04db602dce7bd89 Mon Sep 17 00:00:00 2001 From: James Speirs Date: Thu, 30 Oct 2014 08:59:45 -0600 Subject: [PATCH 3/3] Added tests for complex queries and for querying within document fragments. --- test/testShim.js | 83 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/test/testShim.js b/test/testShim.js index e632355..87102c0 100644 --- a/test/testShim.js +++ b/test/testShim.js @@ -14,21 +14,44 @@ describe('scopedQuerySelectorShim', function() { return node; } + function makeNodeAndAddToFragment(html) { + var frag, node; + frag = document.createDocumentFragment(); + node = makeNode(html); + frag.appendChild(node); + return node; + } + function testChildNode(node) { - expect(node.innerHTML).to.equal('Child'); + expect(node.innerHTML).toBe('Child'); } function testChildNodeList(nodeList) { - expect(nodeList.length).to.equal(1); + expect(nodeList.length).toBe(1); testChildNode(nodeList[0]); } + + function testComplexNodeList(nodeList) { + testGrandChildNode(nodeList[0]); + testGrandChildNode1(nodeList[1]); + testGrandChildNode2(nodeList[2]); + testGrandChildNode3(nodeList[3]); + } function testGrandChildNode(node) { - expect(node.innerHTML).to.equal('Grandchild 1'); + expect(node.innerHTML).toBe('Grandchild 1'); } function testGrandChildNode1(node) { - expect(node.innerHTML).to.equal('Grandchild 2'); + expect(node.innerHTML).toBe('Grandchild 2'); + } + + function testGrandChildNode2(node) { + expect(node.innerHTML).toBe('Grandchild 3'); + } + + function testGrandChildNode3(node) { + expect(node.innerHTML).toBe('Grandchild 4'); } function testGrandChildNodeList(nodeList) { @@ -45,7 +68,7 @@ describe('scopedQuerySelectorShim', function() {
\
Grandchild
\
\ - '; +
'; var listHTML = '
\
    \ @@ -54,6 +77,17 @@ describe('scopedQuerySelectorShim', function() {
\
'; + var complexHTML = '
\ +
    \ +
  • Grandchild 1
  • \ +
  • Grandchild 2
  • \ +
\ +
    \ +
  1. Grandchild 3
  2. \ +
  3. Grandchild 4
  4. \ +
\ +
'; + describe('when nodes are in the DOM', function() { it('should find child nodes', function() { testChildNode(makeNodeAndAddToDOM(childHTML).querySelector(':scope > header')); @@ -64,6 +98,11 @@ describe('scopedQuerySelectorShim', function() { testGrandChildNode(makeNodeAndAddToDOM(listHTML).querySelector(':scope > ul > li')); testGrandChildNodeList(makeNodeAndAddToDOM(listHTML).querySelectorAll(':scope > ul > li')); }); + + it('should handle complex queries', function() { + testGrandChildNode(makeNodeAndAddToDOM(complexHTML).querySelector(':scope > ul > li, :scope > ol > li')); + testComplexNodeList(makeNodeAndAddToDOM(complexHTML).querySelectorAll(':scope > ul > li, :scope > ol > li')); + }); }); describe('when nodes are not in the DOM', function() { @@ -76,30 +115,52 @@ describe('scopedQuerySelectorShim', function() { testGrandChildNode(makeNode(listHTML).querySelector(':scope > ul > li')); testGrandChildNodeList(makeNode(listHTML).querySelectorAll(':scope > ul > li')); }); + + it('should handle complex queries', function() { + testGrandChildNode(makeNode(complexHTML).querySelector(':scope > ul > li, :scope > ol > li')); + testComplexNodeList(makeNode(complexHTML).querySelectorAll(':scope > ul > li, :scope > ol > li')); + }); + }); + + describe('when nodes are in a document fragment', function() { + it('should find child nodes', function() { + testChildNode(makeNodeAndAddToFragment(childHTML).querySelector(':scope > header')); + testChildNodeList(makeNodeAndAddToFragment(childHTML).querySelectorAll(':scope > header')); + }); + + it('should find grandchild nodes', function() { + testGrandChildNode(makeNodeAndAddToFragment(listHTML).querySelector(':scope > ul > li')); + testGrandChildNodeList(makeNodeAndAddToFragment(listHTML).querySelectorAll(':scope > ul > li')); + }); + + it('should handle complex queries', function() { + testGrandChildNode(makeNodeAndAddToFragment(complexHTML).querySelector(':scope > ul > li, :scope > ol > li')); + testComplexNodeList(makeNodeAndAddToFragment(complexHTML).querySelectorAll(':scope > ul > li, :scope > ol > li')); + }); }); describe('when temporary containers and IDs are used', function() { it('should not leave nodes in temporary container', function() { var node = makeNode(childHTML); - expect(node.parentNode).to.be.null; + expect(node.parentNode).toBe(null); node.querySelectorAll(':scope > header'); - expect(node.parentNode).to.be.null; + expect(node.parentNode).toBe(null); }); it('should not leave temporary IDs', function() { var node = makeNode(childHTML); - expect(node.id).to.equal(''); + expect(node.id).toBe(''); node.querySelectorAll(':scope > header'); - expect(node.id).to.equal(''); + expect(node.id).toBe(''); }); }); describe('when nodes have IDs', function() { it('should not overwrite existing IDs', function() { var node = makeNode(idHTML); - expect(node.id).to.equal('myDiv'); + expect(node.id).toBe('myDiv'); node.querySelectorAll(':scope > header'); - expect(node.id).to.equal('myDiv'); + expect(node.id).toBe('myDiv'); }); }); });