Skip to content

Commit 6ed29f0

Browse files
Marcy Suttonmarcysutton
authored andcommitted
feat(aria-required-parent): add Shadow DOM support
Closes #422
1 parent 9bf2870 commit 6ed29f0

File tree

2 files changed

+68
-20
lines changed

2 files changed

+68
-20
lines changed

lib/checks/aria/required-parent.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ function getAriaOwners(element) {
3636
while (element) {
3737
if (element.getAttribute('id')) {
3838
const id = axe.commons.utils.escapeSelector(element.getAttribute('id'));
39-
o = document.querySelector(`[aria-owns~=${id}]`);
39+
let doc = axe.commons.dom.getRootNode(element);
40+
o = doc.querySelector(`[aria-owns~=${id}]`);
4041
if (o) { owners.push(o); }
4142
}
4243
element = element.parentElement;

test/checks/aria/required-parent.js

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ describe('aria-required-parent', function () {
22
'use strict';
33

44
var fixture = document.getElementById('fixture');
5+
var shadowSupported = axe.testUtils.shadowSupport.v1;
56

67
var checkContext = {
78
_data: null,
@@ -10,47 +11,93 @@ describe('aria-required-parent', function () {
1011
}
1112
};
1213

14+
var checkSetup = axe.testUtils.checkSetup;
15+
1316
afterEach(function () {
1417
fixture.innerHTML = '';
1518
checkContext._data = null;
1619
});
1720

1821
it('should detect missing required parent', function () {
19-
fixture.innerHTML = '<div><p role="listitem" id="target">Nothing here.</p></div>';
20-
var node = fixture.querySelector('#target');
21-
assert.isFalse(checks['aria-required-parent'].evaluate.call(checkContext, node));
22+
var params = checkSetup('<div><p role="listitem" id="target">Nothing here.</p></div>');
23+
assert.isFalse(checks['aria-required-parent'].evaluate.apply(checkContext, params));
24+
assert.deepEqual(checkContext._data, ['list']);
25+
});
26+
27+
(shadowSupported ? it : xit)
28+
('should detect missing required parent across shadow boundary', function () {
29+
fixture.innerHTML = '<div id="target"></div>';
30+
31+
var shadowRoot = document.querySelector('#target').attachShadow({ mode: 'open' });
32+
shadowRoot.innerHTML = '<p role="listitem" id="target">Nothing here.</p>';
33+
34+
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
35+
var shadowContent = shadowRoot.querySelector('#target');
36+
var virtualTarget = axe.utils.getNodeFromTree(tree[0], shadowContent);
37+
38+
var params = [shadowContent, undefined, virtualTarget];
39+
assert.isFalse(checks['aria-required-parent'].evaluate.apply(checkContext, params));
2240
assert.deepEqual(checkContext._data, ['list']);
2341
});
2442

2543
it('should pass when required parent is present in an ancestral aria-owns context', function () {
26-
fixture.innerHTML = '<div role="list" ><div aria-owns="parent"></div></div><div id="parent"><p role="listitem" id="target">Nothing here.</p></div>';
27-
var node = fixture.querySelector('#target');
28-
assert.isTrue(checks['aria-required-parent'].evaluate.call(checkContext, node));
44+
var snippet = '<div role="list"><div aria-owns="parent"></div></div>'+
45+
'<div id="parent"><p role="listitem" id="target">Nothing here.</p></div>';
46+
var params = checkSetup(snippet);
47+
assert.isTrue(checks['aria-required-parent'].evaluate.apply(checkContext, params));
2948
});
3049

3150
it('should fail when wrong role is present in an aria-owns context', function () {
32-
fixture.innerHTML = '<div role="menu" ><div aria-owns="target"></div></div><div><p role="listitem" id="target">Nothing here.</p></div>';
33-
var node = fixture.querySelector('#target');
34-
assert.isFalse(checks['aria-required-parent'].evaluate.call(checkContext, node));
51+
var params = checkSetup(
52+
'<div role="menu"><div aria-owns="target"></div></div>' +
53+
'<div><p role="listitem" id="target">Nothing here.</p></div>'
54+
);
55+
assert.isFalse(checks['aria-required-parent'].evaluate.apply(checkContext, params));
3556
assert.deepEqual(checkContext._data, ['list']);
3657
});
3758

38-
3959
it('should pass when required parent is present in an aria-owns context', function () {
40-
fixture.innerHTML = '<div role="list" aria-owns="target"></div><div><p role="listitem" id="target">Nothing here.</p></div>';
41-
var node = fixture.querySelector('#target');
42-
assert.isTrue(checks['aria-required-parent'].evaluate.call(checkContext, node));
60+
var params = checkSetup('<div role="list" aria-owns="target"></div><div><p role="listitem" id="target">Nothing here.</p></div>');
61+
assert.isTrue(checks['aria-required-parent'].evaluate.apply(checkContext, params));
4362
});
4463

4564
it('should pass when at least one required parent of multiple is present', function () {
46-
fixture.innerHTML = '<div role="grid" ><p role="row" id="target">Nothing here.</p></div>';
47-
var node = fixture.querySelector('#target');
48-
assert.isTrue(checks['aria-required-parent'].evaluate.call(checkContext, node));
65+
var params = checkSetup('<div role="grid"><p role="row" id="target">Nothing here.</p></div>');
66+
assert.isTrue(checks['aria-required-parent'].evaluate.apply(checkContext, params));
4967
});
68+
5069
it('should pass when required parent is present', function () {
51-
fixture.innerHTML = '<div role="list" ><p role="listitem" id="target">Nothing here.</p></div>';
52-
var node = fixture.querySelector('#target');
53-
assert.isTrue(checks['aria-required-parent'].evaluate.call(checkContext, node));
70+
var params = checkSetup('<div role="list"><p role="listitem" id="target">Nothing here.</p></div>');
71+
assert.isTrue(checks['aria-required-parent'].evaluate.apply(checkContext, params));
5472
});
5573

74+
(shadowSupported ? it : xit)
75+
('should pass when required parent is present across shadow boundary', function () {
76+
fixture.innerHTML = '<div role="list" id="parent"></div>';
77+
78+
var shadowRoot = document.querySelector('#parent').attachShadow({ mode: 'open' });
79+
shadowRoot.innerHTML = '<p role="listitem" id="target">Nothing here.</p>';
80+
81+
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
82+
var shadowContent = shadowRoot.querySelector('#target');
83+
var virtualTarget = axe.utils.getNodeFromTree(tree[0], shadowContent);
84+
85+
var params = [shadowContent, undefined, virtualTarget];
86+
assert.isTrue(checks['aria-required-parent'].evaluate.apply(checkContext, params));
87+
});
88+
89+
(shadowSupported ? it : xit)
90+
('should fail when aria-owns context crosses shadow boundary', function () {
91+
fixture.innerHTML = '<div id="parent"><div role="list" aria-owns="target"></div></div>';
92+
93+
var shadowRoot = document.querySelector('#parent').attachShadow({ mode: 'open' });
94+
shadowRoot.innerHTML = '<p role="listitem" id="target">Nothing here.</p>';
95+
96+
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
97+
var shadowContent = shadowRoot.querySelector('#target');
98+
var virtualTarget = axe.utils.getNodeFromTree(tree[0], shadowContent);
99+
100+
var params = [shadowContent, undefined, virtualTarget];
101+
assert.isFalse(checks['aria-required-parent'].evaluate.apply(checkContext, params));
102+
});
56103
});

0 commit comments

Comments
 (0)