aria-api

access ARIA information from JavaScript
git clone https://git.ce9e.org/aria-api.git

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 });