- commit
- 9cd78ca822c656b7a6d0ee6cd7171100927ddee9
- parent
- 50c2d3fcb29dc090987b6307c2708a86fb288a37
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2026-02-18 22:04
atree: traverse into shadow DOM
Diffstat
| M | lib/atree.js | 17 | +++++++++++++---- |
| M | test/test-role.js | 44 | ++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 57 insertions, 4 deletions
diff --git a/lib/atree.js b/lib/atree.js
@@ -8,7 +8,7 @@ const _getOwner = function(node) {
8 8 };
9 9
10 10 const _getParentNode = function(node) {
11 -1 return _getOwner(node) || node.parentNode;
-1 11 return _getOwner(node) || node.parentNode || node.host;
12 12 };
13 13
14 14 const detectLoop = function(node) {
@@ -29,7 +29,7 @@ const getOwner = function(node) {
29 29 };
30 30
31 31 export const getParentNode = function(node) {
32 -1 return getOwner(node) || node.parentNode;
-1 32 return getOwner(node) || node.parentNode || node.host;
33 33 };
34 34
35 35 const isHidden = function(node) {
@@ -38,9 +38,18 @@ const isHidden = function(node) {
38 38
39 39 export const getChildNodes = function(node) {
40 40 const childNodes = [];
-1 41 let rawChildNodes = [];
41 42
42 -1 for (let i = 0; i < node.childNodes.length; i++) {
43 -1 const child = node.childNodes[i];
-1 43 if (node.shadowRoot) {
-1 44 rawChildNodes = node.shadowRoot.childNodes;
-1 45 } else if (node.assignedNodes) {
-1 46 rawChildNodes = node.assignedNodes();
-1 47 } else {
-1 48 rawChildNodes = node.childNodes;
-1 49 }
-1 50
-1 51 for (let i = 0; i < rawChildNodes.length; i++) {
-1 52 const child = rawChildNodes[i];
44 53 if (!getOwner(child) && !isHidden(child)) {
45 54 childNodes.push(child);
46 55 }
diff --git a/test/test-role.js b/test/test-role.js
@@ -18,6 +18,18 @@ var LANDMARKS = '<header>banner</header>\n' + 18 18 ' <footer>contentinfo</footer>\n' + 19 19 '</div>'; 20 20 -1 21 var SHADOW_LANDMARK = '<article>\n' + -1 22 ' <host-element>\n' + -1 23 ' <template shadowrootmode="open">\n' + -1 24 ' <header>Header</header>\n' + -1 25 ' <a href="//example.com">\n' + -1 26 ' <slot></slot>\n' + -1 27 ' </a>\n' + -1 28 ' </template>\n' + -1 29 ' <h2>Heading</h2>\n' + -1 30 ' </host-element>'; -1 31 '</article>'; -1 32 21 33 22 34 describe('query', () => { 23 35 var testbed; @@ -80,6 +92,12 @@ describe('query', () => { 80 92 var actual = aria.getRole(testbed.children[0]); 81 93 expect(actual).toEqual('none'); 82 94 }); -1 95 -1 96 xit('applies scoping rules across shadow roots', () => { -1 97 testbed.setHTMLUnsafe(SHADOW_LANDMARK); -1 98 var actual = aria.querySelector(testbed, 'banner'); -1 99 expect(actual).toNotExist(); -1 100 }); 83 101 }); 84 102 85 103 describe('closest', () => { @@ -99,6 +117,13 @@ describe('query', () => { 99 117 100 118 expect(actual).toNotExist(); 101 119 }); -1 120 -1 121 it('works across shadow roots', () => { -1 122 testbed.setHTMLUnsafe(SHADOW_LANDMARK); -1 123 var link = aria.querySelector(testbed, 'link') -1 124 var actual = aria.closest(link, 'article'); -1 125 expect(actual).toExist(); -1 126 }); 102 127 }); 103 128 104 129 describe('querySelectorAll', () => { @@ -128,5 +153,24 @@ describe('query', () => { 128 153 var actual = aria.querySelectorAll(testbed, 'link'); 129 154 expect(actual.length).toEqual(1); 130 155 }); -1 156 -1 157 it('finds elements inside of shadow roots', () => { -1 158 testbed.setHTMLUnsafe(SHADOW_LANDMARK); -1 159 var actual = aria.querySelector(testbed, 'link'); -1 160 expect(actual).toExist(); -1 161 }); -1 162 -1 163 it('finds slotted elements inside of shadow roots', () => { -1 164 testbed.setHTMLUnsafe(SHADOW_LANDMARK); -1 165 var actual = aria.querySelector(testbed, 'heading'); -1 166 expect(actual).toExist(); -1 167 }); -1 168 -1 169 it('finds slotted elements in the place of the slot', () => { -1 170 testbed.setHTMLUnsafe(SHADOW_LANDMARK); -1 171 var link = aria.querySelector(testbed, 'link') -1 172 var actual = aria.querySelector(link, 'heading'); -1 173 expect(actual).toExist(); -1 174 }); 131 175 }); 132 176 });