-
Notifications
You must be signed in to change notification settings - Fork 779
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(utils): new shadowSelectAll utility (#3796)
* feat(utils): new shadowSelectAll utility * Apply suggestions from code review Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com> Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com>
- Loading branch information
1 parent
757a6fc
commit 5865462
Showing
3 changed files
with
125 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/** | ||
* Find elements to match a selector. | ||
* Use an array of selectors to reach into shadow DOM trees | ||
* | ||
* @param {string|string[]} selector String or array of strings with a CSS selector | ||
* @param {Document} doc Optional document node | ||
* @returns {Node[]} | ||
*/ | ||
export default function shadowSelectAll(selectors, doc = document) { | ||
// Spread to avoid mutating the input | ||
const selectorArr = Array.isArray(selectors) ? [...selectors] : [selectors]; | ||
if (selectors.length === 0) { | ||
return []; | ||
} | ||
return selectAllRecursive(selectorArr, doc); | ||
} | ||
|
||
/* Find elements in shadow or light DOM trees, using an array of selectors */ | ||
function selectAllRecursive([selectorStr, ...restSelector], doc) { | ||
const elms = doc.querySelectorAll(selectorStr); | ||
if (restSelector.length === 0) { | ||
return Array.from(elms); | ||
} | ||
const selected = []; | ||
for (const elm of elms) { | ||
if (elm?.shadowRoot) { | ||
selected.push(...selectAllRecursive(restSelector, elm.shadowRoot)); | ||
} | ||
} | ||
return selected; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
describe('utils.shadowSelectAll', () => { | ||
const shadowSelectAll = axe.utils.shadowSelectAll; | ||
const fixture = document.querySelector('#fixture'); | ||
const mapNodeName = elms => elms.map(elm => elm.nodeName.toLowerCase()); | ||
|
||
it('throws when not passed a string or array', () => { | ||
assert.throws(() => { | ||
shadowSelectAll(123); | ||
}); | ||
}); | ||
|
||
it('throws when passed an array with non-string values', () => { | ||
assert.throws(() => { | ||
shadowSelectAll([123]); | ||
}); | ||
}); | ||
|
||
describe('given a string', () => { | ||
it('returns [] if no node is found', () => { | ||
fixture.innerHTML = '<b class="hello"></b>'; | ||
assert.deepEqual(shadowSelectAll('.goodbye'), []); | ||
}); | ||
|
||
it('returns the each matching element in the document', () => { | ||
fixture.innerHTML = `<b class="hello"></b> | ||
<s class="goodbye"></s> | ||
<i class="hello"></i>`; | ||
const nodes = shadowSelectAll('#fixture > .hello'); | ||
assert.deepEqual(mapNodeName(nodes), ['b', 'i']); | ||
}); | ||
}); | ||
|
||
describe('given an array of string', () => { | ||
function addShadowTree(host, html) { | ||
const root = host.attachShadow({ mode: 'open' }); | ||
root.innerHTML = html; | ||
return root; | ||
} | ||
|
||
it('returns [] given an empty array', () => { | ||
assert.deepEqual(shadowSelectAll([]), []); | ||
}); | ||
|
||
it('returns [] if the shadow host does not exist', () => { | ||
fixture.innerHTML = '<div></div>'; | ||
addShadowTree(fixture.children[0], `<b></b>`); | ||
assert.deepEqual(shadowSelectAll(['#fixture > span', 'b']), []); | ||
}); | ||
|
||
it('returns [] if the no final element exists', () => { | ||
fixture.innerHTML = '<span></span>'; | ||
addShadowTree(fixture.children[0], `<i></i>`); | ||
assert.deepEqual(shadowSelectAll(['span', 'b']), []); | ||
}); | ||
|
||
it('returns nodes from a shadow tree', () => { | ||
fixture.innerHTML = '<span></span>'; | ||
addShadowTree(fixture.children[0], `<b></b><i></i>`); | ||
const nodeNames = mapNodeName(shadowSelectAll(['#fixture > span', '*'])); | ||
assert.deepEqual(nodeNames, ['b', 'i']); | ||
}); | ||
|
||
it('returns nodes from multiple shadow trees', () => { | ||
fixture.innerHTML = '<span></span><span></span>'; | ||
addShadowTree(fixture.children[0], `<a></a><b></b>`); | ||
addShadowTree(fixture.children[1], `<i></i><s></s>`); | ||
const nodeNames = mapNodeName(shadowSelectAll(['#fixture > span', '*'])); | ||
assert.deepEqual(nodeNames, ['a', 'b', 'i', 's']); | ||
}); | ||
|
||
it('returns nodes from multiple trees deep', () => { | ||
fixture.innerHTML = '<div></div><div></div>'; | ||
const root1 = addShadowTree( | ||
fixture.children[0], | ||
'<span></span><span></span>' | ||
); | ||
const root2 = addShadowTree( | ||
fixture.children[1], | ||
'<div></div><span></span>' | ||
); | ||
|
||
addShadowTree(root1.children[0], '<a></a>'); | ||
addShadowTree(root1.children[1], '<b></b>'); | ||
addShadowTree(root2.children[0], '<i></i>'); | ||
addShadowTree(root2.children[1], '<s></s>'); | ||
|
||
const nodeNames = mapNodeName( | ||
shadowSelectAll(['#fixture > div', 'span', '*']) | ||
); | ||
assert.deepEqual(nodeNames, ['a', 'b', 's']); | ||
}); | ||
}); | ||
}); |