From 5f21fe889592d4b92e8a0bb99fcb8f65fa81e89b Mon Sep 17 00:00:00 2001 From: Jey Date: Fri, 8 Jun 2018 18:10:18 +0100 Subject: [PATCH 1/5] fix: list and list items fixes for role changes. --- lib/checks/lists/dlitem.js | 23 ++++- lib/checks/lists/listitem.js | 18 +++- lib/checks/lists/only-dlitems.js | 52 +++++++--- lib/checks/lists/only-listitems.js | 98 ++++++++++++++++--- test/checks/lists/dlitem.js | 19 +++- test/checks/lists/listitem.js | 10 +- test/checks/lists/only-dlitems.js | 17 ++++ test/checks/lists/only-listitems.js | 35 +++++++ .../definition-list/definition-list.html | 15 ++- .../definition-list/definition-list.json | 2 +- test/integration/rules/dlitem/dlitem.html | 7 +- test/integration/rules/dlitem/dlitem.json | 2 +- test/integration/rules/list/list.html | 63 +++++++++--- test/integration/rules/list/list.json | 6 +- test/integration/rules/listitem/listitem.html | 21 +++- test/integration/rules/listitem/listitem.json | 2 +- 16 files changed, 321 insertions(+), 69 deletions(-) diff --git a/lib/checks/lists/dlitem.js b/lib/checks/lists/dlitem.js index 80a6608fc7..e3b9bbd97e 100644 --- a/lib/checks/lists/dlitem.js +++ b/lib/checks/lists/dlitem.js @@ -1,2 +1,21 @@ -var parent = axe.commons.dom.getComposedParent(node); -return parent.nodeName.toUpperCase() === 'DL'; +const parent = axe.commons.dom.getComposedParent(node); + +if (!parent) { + return false; +} + +const ALLOWED_ROLES = ['list']; +const parentTagName = parent.nodeName.toLowerCase(); +const parentRole = (parent.getAttribute('role') || '').toLowerCase(); + +if (parentTagName === 'dl') { + if (parentRole) { + if (ALLOWED_ROLES.includes(parentRole)) { + return true; + } + return false; + } + return true; +} + +return false; diff --git a/lib/checks/lists/listitem.js b/lib/checks/lists/listitem.js index 1a7abcb40d..758282d876 100644 --- a/lib/checks/lists/listitem.js +++ b/lib/checks/lists/listitem.js @@ -1,4 +1,14 @@ -var parent = axe.commons.dom.getComposedParent(node); -return (['UL', 'OL'].includes(parent.nodeName.toUpperCase()) || - (parent.getAttribute('role') || '').toLowerCase() === 'list'); - \ No newline at end of file +const parent = axe.commons.dom.getComposedParent(node); + +if (!parent) { + return false; +} + +const ALLOWED_TAGS = ['ul', 'ol']; +const ALLOWED_ROLES = ['list']; + +const parentTagName = parent.nodeName.toLowerCase(); +const parentRole = (parent.getAttribute('role') || '').toLowerCase(); +const isListRole = (ALLOWED_ROLES.includes(parentRole)); + +return ((ALLOWED_TAGS.includes(parentTagName) && (!parentRole || isListRole)) ||isListRole); diff --git a/lib/checks/lists/only-dlitems.js b/lib/checks/lists/only-dlitems.js index ff3a20d62d..537c8d7588 100644 --- a/lib/checks/lists/only-dlitems.js +++ b/lib/checks/lists/only-dlitems.js @@ -1,19 +1,41 @@ -var bad = [], - permitted = ['STYLE', 'META', 'LINK', 'MAP', 'AREA', 'SCRIPT', 'DATALIST', 'TEMPLATE'], - hasNonEmptyTextNode = false; -virtualNode.children.forEach(({ actualNode }) => { - var nodeName = actualNode.nodeName.toUpperCase(); - if (actualNode.nodeType === 1 && nodeName !== 'DT' && nodeName !== 'DD' && permitted.indexOf(nodeName) === -1) { - bad.push(actualNode); - } else if (actualNode.nodeType === 3 && actualNode.nodeValue.trim() !== '') { - hasNonEmptyTextNode = true; - } -}); +const ALLOWED_TAGS = [ + 'style', + 'meta', + 'link', + 'map', + 'area', + 'script', + 'datalist', + 'template' +]; -if (bad.length) { - this.relatedNodes(bad); +const ALLOWED_ROLES = ['definition', 'term', 'list']; + +let base = { + badNodes: [], + hasNonEmptyTextNode: false +}; + +const out = virtualNode.children + .reduce((out, { actualNode }) => { + const tagName = actualNode.nodeName.toLowerCase(); + if (actualNode.nodeType === 1 && ALLOWED_TAGS.indexOf(tagName) === -1) { + const role = (actualNode.getAttribute('role') || '').toLowerCase(); + if ((tagName !== 'dt' && tagName !== 'dd') || role) { + if (!(ALLOWED_ROLES.includes(role))) { // handle comment - https://github.com/dequelabs/axe-core/pull/518/files#r139284668 + out.badNodes.push(actualNode); + } + } + } else if (actualNode.nodeType === 3 && + actualNode.nodeValue.trim() !== '') { + out.hasNonEmptyTextNode = true; + } + return out; + }, base); + +if (out.badNodes.length) { + this.relatedNodes(out.badNodes); } -var retVal = !!bad.length || hasNonEmptyTextNode; -return retVal; +return !!out.badNodes.length || out.hasNonEmptyTextNode; \ No newline at end of file diff --git a/lib/checks/lists/only-listitems.js b/lib/checks/lists/only-listitems.js index bdb53064e5..2f7333d529 100644 --- a/lib/checks/lists/only-listitems.js +++ b/lib/checks/lists/only-listitems.js @@ -1,18 +1,84 @@ -var bad = [], - permitted = ['STYLE', 'META', 'LINK', 'MAP', 'AREA', 'SCRIPT', 'DATALIST', 'TEMPLATE'], - hasNonEmptyTextNode = false; - -virtualNode.children.forEach(({ actualNode }) => { - var nodeName = actualNode.nodeName.toUpperCase(); - if (actualNode.nodeType === 1 && nodeName !== 'LI' && permitted.indexOf(nodeName) === -1) { - bad.push(actualNode); - } else if (actualNode.nodeType === 3 && actualNode.nodeValue.trim() !== '') { - hasNonEmptyTextNode = true; - } -}); - -if (bad.length) { - this.relatedNodes(bad); +const ALLOWED_TAGS = [ + 'style', + 'meta', + 'link', + 'map', + 'area', + 'script', + 'datalist', + 'template' +]; + +const getIsListItemRole = (r, t) => { + let out = false; + out = (r === 'listitem' || (t === 'li' && !r)); + return out; +}; + +const getHasListItem = (hasListItem, tagName, isListItemRole) => { + return hasListItem || (tagName === 'li' && isListItemRole) || isListItemRole; +} + +let base = { + badNodes: [], + isEmpty: true, + hasNonEmptyTextNode: false, + hasListItem: false, + liItemsWithRole: 0 +}; + +let out = virtualNode.children + .reduce((out, { actualNode }) => { + /*eslint + complexity: ["error", 11] + */ + const tagName = actualNode.nodeName.toLowerCase(); + + switch (actualNode.nodeType) { + case 1: + if (!ALLOWED_TAGS.includes(tagName)) { + const role = (actualNode.getAttribute('role') || '').toLowerCase(); + const isListItemRole = getIsListItemRole(role, tagName); + + out.hasListItem = getHasListItem(out.hasListItem, tagName, isListItemRole); + + if (isListItemRole) { + out.isEmpty = false; + } + if (tagName === 'li' && !isListItemRole) { + out.liItemsWithRole++; + } + if (tagName !== 'li' && !isListItemRole) { + out.badNodes.push(actualNode); + } + } + break; + case 3: + if (actualNode.nodeValue.trim() !== '') { + out.hasNonEmptyTextNode = true; + } + break; + default: + break; + } + return out; + }, base); + +const virtualNodeChildrenOfTypeLi = virtualNode.children + .filter(({ actualNode }) => { + return actualNode.nodeName.toLowerCase() === 'li' + }); + +const allLiItemsHaveRole = out.liItemsWithRole > 0 && + virtualNodeChildrenOfTypeLi.length === out.liItemsWithRole; + +if (out.badNodes.length) { + this.relatedNodes(out.badNodes); } -return !!bad.length || hasNonEmptyTextNode; +return ( + !(out.hasListItem || + (out.isEmpty && !allLiItemsHaveRole)) || + !!out.badNodes.length || + out.hasNonEmptyTextNode +); diff --git a/test/checks/lists/dlitem.js b/test/checks/lists/dlitem.js index 56f7596b36..77e7f144b8 100644 --- a/test/checks/lists/dlitem.js +++ b/test/checks/lists/dlitem.js @@ -15,12 +15,27 @@ describe('dlitem', function () { assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs)); }); - it('should fail if the dlitem has an incorrect parent', function () { + it('should fail if the dt element has an incorrect parent', function () { var checkArgs = checkSetup('