-
Notifications
You must be signed in to change notification settings - Fork 779
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: check for role changes in lists and list items #518
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
var parent = axe.commons.dom.getComposedParent(node); | ||
return parent.nodeName.toUpperCase() === 'DL'; | ||
return parent.nodeName.toUpperCase() === 'DL' && !parent.getAttribute('role'); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,11 @@ | ||
var parent = axe.commons.dom.getComposedParent(node); | ||
return (['UL', 'OL'].includes(parent.nodeName.toUpperCase()) || | ||
(parent.getAttribute('role') || '').toLowerCase() === 'list'); | ||
|
||
|
||
var parentRole = (parent.getAttribute('role') || '').toLowerCase(); | ||
|
||
var isListRole = parentRole === 'list'; | ||
|
||
return ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't follow our spacing convention. It's also a little hard to read. Can you reformat? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @WilcoFiers which spacing convention are you referring to? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
(['UL', 'OL'].includes(parent.nodeName.toUpperCase()) && | ||
(!parentRole || isListRole)) || | ||
isListRole | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,8 +4,12 @@ var bad = [], | |
|
||
virtualNode.children.forEach(({ actualNode }) => { | ||
var nodeName = actualNode.nodeName.toUpperCase(); | ||
if (actualNode.nodeType === 1 && nodeName !== 'DT' && nodeName !== 'DD' && permitted.indexOf(nodeName) === -1) { | ||
bad.push(actualNode); | ||
|
||
if (actualNode.nodeType === 1 && permitted.indexOf(nodeName) === -1) { | ||
var role = (actualNode.getAttribute('role') || '').toLowerCase(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should allow |
||
if ((nodeName !== 'DT' && nodeName !== 'DD') || role) { | ||
bad.push(actualNode); | ||
} | ||
} else if (actualNode.nodeType === 3 && actualNode.nodeValue.trim() !== '') { | ||
hasNonEmptyTextNode = true; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,41 @@ | ||
var bad = [], | ||
permitted = ['STYLE', 'META', 'LINK', 'MAP', 'AREA', 'SCRIPT', 'DATALIST', 'TEMPLATE'], | ||
hasNonEmptyTextNode = false; | ||
hasNonEmptyTextNode = false, | ||
hasListItem = false, | ||
liItemsWithRole = 0, | ||
isEmpty = true; | ||
|
||
virtualNode.children.forEach(({ actualNode }) => { | ||
var nodeName = actualNode.nodeName.toUpperCase(); | ||
if (actualNode.nodeType === 1 && nodeName !== 'LI' && permitted.indexOf(nodeName) === -1) { | ||
bad.push(actualNode); | ||
var isListItemRole = false; | ||
|
||
if (actualNode.nodeType === 1 && permitted.indexOf(nodeName) === -1) { | ||
var role = (actualNode.getAttribute('role') || '').toLowerCase(); | ||
isListItemRole = role === 'listitem' || (nodeName === 'LI' && !role); | ||
hasListItem = hasListItem || (nodeName === 'LI' && isListItemRole) || isListItemRole; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs either a comment, or it needs to be cleared up a bit, because it's not immediately obvious what the if statements in this block do. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It also seems a little redundant: |
||
|
||
if (isListItemRole) { | ||
isEmpty = false; | ||
} | ||
|
||
if (nodeName === 'LI' && !isListItemRole) { | ||
liItemsWithRole++; | ||
} | ||
|
||
if (nodeName !== 'LI' && !isListItemRole) { | ||
bad.push(actualNode); | ||
} | ||
|
||
} else if (actualNode.nodeType === 3 && actualNode.nodeValue.trim() !== '') { | ||
hasNonEmptyTextNode = true; | ||
} | ||
}); | ||
|
||
var allLiItemsHaveRole = liItemsWithRole > 0 && virtualNode.children.filter(({ actualNode }) => | ||
actualNode.nodeName === 'LI').length === liItemsWithRole; | ||
|
||
if (bad.length) { | ||
this.relatedNodes(bad); | ||
} | ||
|
||
return !!bad.length || hasNonEmptyTextNode; | ||
return !(hasListItem || (isEmpty && !allLiItemsHaveRole)) || !!bad.length || hasNonEmptyTextNode; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,12 @@ describe('dlitem', function () { | |
assert.isFalse(checks.dlitem.evaluate.apply(null, checkArgs)); | ||
}); | ||
|
||
it('should fail if the dlitem has a parent <dl> with a changed role', function(){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be "should fail if the dt element has a parent
|
||
var checkArgs = checkSetup('<dl role="menubar"><dt id="target">My list item</dl>'); | ||
|
||
assert.isFalse(checks.dlitem.evaluate.apply(null, checkArgs)); | ||
}); | ||
|
||
(shadowSupport.v1 ? it : xit)('should return true in a shadow DOM pass', function () { | ||
var node = document.createElement('div'); | ||
node.innerHTML = '<dt>My list item </dt>'; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,18 @@ describe('only-dlitems', function () { | |
assert.deepEqual(checkContext._relatedNodes, [fixture.querySelector('p')]); | ||
}); | ||
|
||
it('should return true if the list has non-dd content through role change', function(){ | ||
var checkArgs = checkSetup('<dl id="target"><dd role="menuitem">Not a list</dd></dl>'); | ||
|
||
assert.isTrue(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return true if the list has non-dt content through role change', function(){ | ||
var checkArgs = checkSetup('<dl id="target"><dt role="menuitem">Not a list</dt></dl>'); | ||
|
||
assert.isTrue(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return false if the list has only a dd', function () { | ||
var checkArgs = checkSetup('<dl id="target"><dd>A list</dd></dl>'); | ||
|
||
|
@@ -80,30 +92,60 @@ describe('only-dlitems', function () { | |
assert.isFalse(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return true if <link> is used along side dt with its role changed', function () { | ||
var checkArgs = checkSetup('<dl id="target"><link rel="stylesheet" href="theme.css"><dt role="menuitem">A list</dt></dl>'); | ||
|
||
assert.isTrue(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return false if <meta> is used along side dt', function () { | ||
var checkArgs = checkSetup('<dl id="target"><meta name="description" content=""><dt>A list</dt></dl>'); | ||
|
||
assert.isFalse(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return true if <meta> is used along side dt with its role changed', function () { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test seems unnecessary, and so do the new tests below this one. |
||
var checkArgs = checkSetup('<dl id="target"><meta name="description" content=""><dt role="menuitem">A list</dt></dl>'); | ||
|
||
assert.isTrue(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return false if <script> is used along side dt', function () { | ||
var checkArgs = checkSetup('<dl id="target"><script src="script.js"></script><dt>A list</dt></dl>'); | ||
|
||
assert.isFalse(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return true if <script> is used along side dt with its role changed', function () { | ||
var checkArgs = checkSetup('<dl id="target"><script src="script.js"></script><dt role="menuitem">A list</dt></dl>'); | ||
|
||
assert.isTrue(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return false if <style> is used along side dt', function () { | ||
var checkArgs = checkSetup('<dl id="target"><style></style><dt>A list</dt></dl>'); | ||
|
||
assert.isFalse(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return true if <style> is used along side dt with its role changed', function () { | ||
var checkArgs = checkSetup('<dl id="target"><style></style><dt role="menuitem">A list</dt></dl>'); | ||
|
||
assert.isTrue(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return false if <template> is used along side dt', function () { | ||
var checkArgs = checkSetup('<dl id="target"><template></template><dt>A list</dt></dl>'); | ||
|
||
assert.isFalse(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return true if <template> is used along side dt with its role changed', function () { | ||
var checkArgs = checkSetup('<dl id="target"><template></template><dt role="menuitem">A list</dt></dl>'); | ||
|
||
assert.isTrue(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
(shadowSupport.v1 ? it : xit)('should return false in a shadow DOM pass', function () { | ||
var node = document.createElement('div'); | ||
node.innerHTML = '<dt>My list item </dt>'; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,12 @@ describe('only-listitems', function () { | |
assert.isFalse(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return false if the list has only spaces as content', function () { | ||
var checkArgs = checkSetup('<ol id="target"> </ol>'); | ||
|
||
assert.isFalse(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return false if the list has whitespace', function () { | ||
var checkArgs = checkSetup('<ol id="target"><li>Item</li> </ol>'); | ||
|
||
|
@@ -60,43 +66,97 @@ describe('only-listitems', function () { | |
assert.isFalse(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return false if the list has only an element with role listitem', function () { | ||
var checkArgs = checkSetup('<ol id="target"><div role="listitem">A list</div></ol>'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should add tests for multiple listitem elms, and for mixing li and listitem. |
||
|
||
assert.isFalse(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return true if the list has an li with other content', function () { | ||
var checkArgs = checkSetup('<ol id="target"><li>A list</li><p>Not a list</p></ol>'); | ||
|
||
assert.isTrue(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
assert.deepEqual(checkContext._relatedNodes, [fixture.querySelector('p')]); | ||
}); | ||
|
||
it('should return true if the list has only li items with their roles changed', function () { | ||
var checkArgs = checkSetup('<ol id="target"><li role="menuitem">Not a list item</li><li role="menuitem">Not a list item</li></ol>'); | ||
|
||
assert.isTrue(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return false if the list has at least one li while others have their roles changed', function () { | ||
var checkArgs = checkSetup('<ol id="target"><li >A list item</li><li role="menuitem">Not a list item</li></ol>'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dylanb I remember you suggesting this should be a violation? My initial thought about these was that there might be roles we should allow. separator for instance would be one I think is very reasonable. Menuitem itself would fail here because it isn't owned by a menu, so that's its own error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this example does not represent a violation IMO |
||
|
||
assert.isFalse(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return false if <link> is used along side li', function () { | ||
var checkArgs = checkSetup('<ol id="target"><link rel="stylesheet" href="theme.css"><li>A list</li></ol>'); | ||
|
||
assert.isFalse(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return true if <link> is used along side only li items with their roles changed', function () { | ||
var checkArgs = checkSetup('<ol id="target"><link rel="stylesheet" href="theme.css"><li role="menuitem">Not a list item</li></ol>'); | ||
|
||
assert.isTrue(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return false if <meta> is used along side li', function () { | ||
var checkArgs = checkSetup('<ol id="target"><meta name="description" content=""><li>A list</li></ol>'); | ||
|
||
assert.isFalse(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return true if <meta> is used along side only li items with their roles changed', function () { | ||
var checkArgs = checkSetup('<ol id="target"><meta name="description" content=""><li role="menuitem">Not a list item</li></ol>'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one doesn't seem useful, and neither do tests added below this one. |
||
|
||
assert.isTrue(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return false if <script> is used along side li', function () { | ||
var checkArgs = checkSetup('<ol id="target"><script src="script.js"></script><li>A list</li></ol>'); | ||
|
||
assert.isFalse(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return true if <script> is used along side only li items with their roles changed', function () { | ||
var checkArgs = checkSetup('<ol id="target"><script src="script.js"></script><li role="menuitem">Not a list item</li></ol>'); | ||
|
||
assert.isTrue(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return false if <style> is used along side li', function () { | ||
var checkArgs = checkSetup('<ol id="target"><style></style><li>A list</li></ol>'); | ||
|
||
assert.isFalse(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return true if <style> is used along side only li items with their roles changed', function () { | ||
var checkArgs = checkSetup('<ol id="target"><style></style><li role="menuitem">Not a list item</li></ol>'); | ||
|
||
assert.isTrue(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return false if <template> is used along side li', function () { | ||
var checkArgs = checkSetup('<ol id="target"><template></template><li>A list</li></ol>'); | ||
|
||
assert.isFalse(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return true if <template> is used along side only li items with their roles changed', function () { | ||
var checkArgs = checkSetup('<ol id="target"><template></template><li role="menuitem">Not a list item</li></ol>'); | ||
|
||
assert.isTrue(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('should return false if the list has only allowed non-li tags as content', function () { | ||
var checkArgs = checkSetup('<ol id="target"><template></template><style></style></ol>'); | ||
|
||
assert.isFalse(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
(shadowSupport.v1 ? it : xit)('should return false in a shadow DOM pass', function () { | ||
var node = document.createElement('div'); | ||
node.innerHTML = '<li>My list item </li>'; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
{ | ||
"description": "definition-list test", | ||
"rule": "definition-list", | ||
"violations": [["#divdl"], ["#mixeddl"], ["#unordereddl"], ["#mixedscriptdl"]], | ||
"violations": [["#divdl"], ["#mixeddl"], ["#unordereddl"], ["#mixedscriptdl"], | ||
["#ddrolechanged"], ["#dtrolechanged"]], | ||
"passes": [["#emptydl"], ["#repeatdl"], ["#properdl"], ["#scriptdl"], ["#emptyscriptdl"]] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"description": "dlitem test", | ||
"rule": "dlitem", | ||
"violations": [["#uncontained"], ["#alsouncontained"]], | ||
"violations": [["#uncontained"], ["#alsouncontained"],["#uncontainedbyrole"],["#alsouncontainedbyrole"]], | ||
"passes": [["#contained"], ["#alsocontained"]] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
|
||
<li id="uncontained">Should belong to a list.</li> | ||
<ul><li id="contained">Does belong to a list.</li></ul> | ||
<ol><li id="alsocontained">Also belongs toa list.</li></ol> | ||
<ol><li id="alsocontained">Also belongs to a list.</li></ol> | ||
<ul role="menubar"><li id="ulrolechanged">Also does not belong to a list.</li></ul> | ||
<ol role="menubar"><li id="olrolechanged">I too do not belong to a list.</li></ol> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"description": "listitem test", | ||
"rule": "listitem", | ||
"violations": [["#uncontained"]], | ||
"violations": [["#uncontained"], ["#ulrolechanged"], ["#olrolechanged"]], | ||
"passes": [["#contained"], ["#alsocontained"]] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this allow
<dl role="list">
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which would say it's okay to overload a
<dl>
with an ARIA role. Is that something we want to encourage? Wouldn't that also depend on the child items havingrole="listitem"
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done