From 12666dc871dd5ed935367a6a0fb42af97f5fb51b Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Tue, 27 Sep 2022 13:15:40 +0200 Subject: [PATCH 1/3] fix(td-headers-attr): ignore table elements with their role changed --- lib/checks/tables/td-headers-attr-evaluate.js | 3 +- lib/rules/table-or-grid-role-matches.js | 6 + lib/rules/td-headers-attr.json | 1 + test/checks/tables/td-headers-attr.js | 148 +++++++++--------- .../td-headers-attr/td-headers-attr.html | 45 ++++++ .../td-headers-attr/td-headers-attr.json | 6 + .../table-or-grid-role-matches.js | 51 ++++++ 7 files changed, 186 insertions(+), 74 deletions(-) create mode 100644 lib/rules/table-or-grid-role-matches.js create mode 100644 test/integration/rules/td-headers-attr/td-headers-attr.html create mode 100644 test/integration/rules/td-headers-attr/td-headers-attr.json create mode 100644 test/rule-matches/table-or-grid-role-matches.js diff --git a/lib/checks/tables/td-headers-attr-evaluate.js b/lib/checks/tables/td-headers-attr-evaluate.js index e781330794..caef8f9a3a 100644 --- a/lib/checks/tables/td-headers-attr-evaluate.js +++ b/lib/checks/tables/td-headers-attr-evaluate.js @@ -1,4 +1,5 @@ import { tokenList } from '../../core/utils'; +import { isVisibleForScreenreader } from '../../commons/dom'; function tdHeadersAttrEvaluate(node) { const cells = []; @@ -24,7 +25,7 @@ function tdHeadersAttrEvaluate(node) { let isSelf = false; let notOfTable = false; - if (!cell.hasAttribute('headers')) { + if (!cell.hasAttribute('headers') || !isVisibleForScreenreader(cell)) { return; } diff --git a/lib/rules/table-or-grid-role-matches.js b/lib/rules/table-or-grid-role-matches.js new file mode 100644 index 0000000000..1443b0acdf --- /dev/null +++ b/lib/rules/table-or-grid-role-matches.js @@ -0,0 +1,6 @@ +import { getRole } from '../commons/aria'; + +export default function tableOrGridRoleMatches(_, vNode) { + const role = getRole(vNode); + return ['treegrid', 'grid', 'table'].includes(role); +} diff --git a/lib/rules/td-headers-attr.json b/lib/rules/td-headers-attr.json index b3104d0f7c..141c4a0933 100644 --- a/lib/rules/td-headers-attr.json +++ b/lib/rules/td-headers-attr.json @@ -1,6 +1,7 @@ { "id": "td-headers-attr", "selector": "table", + "matches": "table-or-grid-role-matches", "tags": ["cat.tables", "wcag2a", "wcag131", "section508", "section508.22.g"], "actIds": ["a25f45"], "metadata": { diff --git a/test/checks/tables/td-headers-attr.js b/test/checks/tables/td-headers-attr.js index f9ba57dd84..83db2b936a 100644 --- a/test/checks/tables/td-headers-attr.js +++ b/test/checks/tables/td-headers-attr.js @@ -1,124 +1,126 @@ -describe('td-headers-attr', function() { +describe('td-headers-attr', function () { 'use strict'; var fixture = document.getElementById('fixture'); var checkContext = axe.testUtils.MockCheckContext(); + var fixtureSetup = axe.testUtils.fixtureSetup; + var check = axe.testUtils.getCheckEvaluate('td-headers-attr'); - afterEach(function() { - fixture.innerHTML = ''; + afterEach(function () { checkContext.reset(); }); - it('returns true no headers attribute is present', function() { - fixture.innerHTML = + it('returns true no headers attribute is present', function () { + fixtureSetup( '' + - ' ' + - ' ' + - '
hi hello
hi hello
'; + ' hi hello ' + + ' hi hello ' + + '' + ); var node = fixture.querySelector('table'); - assert.isTrue( - axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) - ); + assert.isTrue(check.call(checkContext, node)); }); - it('returns true if a valid header is present', function() { - fixture.innerHTML = + it('returns true if a valid header is present', function () { + fixtureSetup( '' + - ' ' + - ' ' + - '
hello
goodbye
'; + ' hello ' + + ' goodbye ' + + '' + ); var node = fixture.querySelector('table'); - assert.isTrue( - axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) - ); + assert.isTrue(check.call(checkContext, node)); }); - it('returns true if multiple valid headers are present', function() { - fixture.innerHTML = + it('returns true if multiple valid headers are present', function () { + fixtureSetup( '' + - ' ' + - ' ' + - '
hello hello
goodbye
'; + ' hello hello ' + + ' goodbye ' + + '' + ); var node = fixture.querySelector('table'); - assert.isTrue( - axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) - ); + assert.isTrue(check.call(checkContext, node)); }); - it('returns true with an empty header', function() { - fixture.innerHTML = + it('returns true with an empty header', function () { + fixtureSetup( '' + - ' ' + - ' ' + - '
goodbye
'; + ' ' + + ' goodbye ' + + '' + ); var node = fixture.querySelector('table'); - assert.isTrue( - axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) - ); + assert.isTrue(check.call(checkContext, node)); }); - it('returns undefined if headers is empty', function() { - fixture.innerHTML = + it('returns undefined if headers is empty', function () { + fixtureSetup( '' + - ' ' + - ' ' + - '
goodbye
'; + ' ' + + ' goodbye ' + + '' + ); var node = fixture.querySelector('table'); - assert.isUndefined( - axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) - ); + assert.isUndefined(check.call(checkContext, node)); }); - it('returns false if the header is a table cell', function() { + it('returns false if the header is a table cell', function () { var node; - fixture.innerHTML = + fixtureSetup( '' + - ' ' + - ' ' + - '
hello
goodbye
'; - node = fixture.querySelector('table'); - assert.isFalse( - axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) + ' hello ' + + ' goodbye ' + + '' ); + node = fixture.querySelector('table'); + assert.isFalse(check.call(checkContext, node)); - fixture.innerHTML = + fixtureSetup( 'hello' + - '' + - ' ' + - ' ' + - '
goodbye
'; - node = fixture.querySelector('table'); - assert.isFalse( - axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) + '' + + ' ' + + ' ' + + '
goodbye
' ); + node = fixture.querySelector('table'); + assert.isFalse(check.call(checkContext, node)); - fixture.innerHTML = + fixtureSetup( '' + - ' ' + - ' ' + - '
hello
goodbye
'; - node = fixture.querySelector('table'); - assert.isFalse( - axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) + ' hello ' + + ' goodbye ' + + '' ); + node = fixture.querySelector('table'); + assert.isFalse(check.call(checkContext, node)); }); - it('returns false if the header refers to the same cell', function() { - fixture.innerHTML = + it('returns false if the header refers to the same cell', function () { + fixtureSetup( '' + - ' ' + - ' ' + - '
hello
goodbye
'; + ' hello ' + + ' goodbye ' + + '' + ); var node = fixture.querySelector('table'); - assert.isFalse( - axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) + assert.isFalse(check.call(checkContext, node)); + }); + + it('returns true if td[headers] is hidden', function () { + fixtureSetup( + '' + + ' ' + + '
Hello
' ); + var node = fixture.querySelector('table'); + assert.isTrue(check.call(checkContext, node)); }); }); diff --git a/test/integration/rules/td-headers-attr/td-headers-attr.html b/test/integration/rules/td-headers-attr/td-headers-attr.html new file mode 100644 index 0000000000..64fc6cb33c --- /dev/null +++ b/test/integration/rules/td-headers-attr/td-headers-attr.html @@ -0,0 +1,45 @@ + + + +
HelloWorld
+ + + + +
HelloWorld
+ + + + + +
HelloHelloWorld
+ + + + +
Hello
+ + + + +
HelloWorld
+ + + +
World
+ + + +
World
+ + + +
World
+ + + + + + + +
World
diff --git a/test/integration/rules/td-headers-attr/td-headers-attr.json b/test/integration/rules/td-headers-attr/td-headers-attr.json new file mode 100644 index 0000000000..e687869b6b --- /dev/null +++ b/test/integration/rules/td-headers-attr/td-headers-attr.json @@ -0,0 +1,6 @@ +{ + "description": "td-headers-attr test", + "rule": "td-headers-attr", + "violations": [["#fail1"], ["#fail2"], ["#fail3"]], + "passes": [["#pass1"], ["#pass2"], ["#pass3"], ["#pass4"]] +} diff --git a/test/rule-matches/table-or-grid-role-matches.js b/test/rule-matches/table-or-grid-role-matches.js new file mode 100644 index 0000000000..9d196ec3f1 --- /dev/null +++ b/test/rule-matches/table-or-grid-role-matches.js @@ -0,0 +1,51 @@ +describe('table-or-grid-role-matches', () => { + const { queryFixture } = axe.testUtils; + const rule = axe.utils.getRule('td-headers-attr'); + + it(`returns true for tables without role`, () => { + const vNode = queryFixture(` + +
foo bar
`); + assert.isTrue(rule.matches(vNode.actualNode, vNode)); + }); + + ['table', 'grid', 'treegrid'].forEach(role => { + it(`returns true for tables with role=${role}`, () => { + const vNode = queryFixture(` + +
foo bar
`); + assert.isTrue(rule.matches(vNode.actualNode, vNode)); + }); + }); + + ['region', 'presentation', 'none'].forEach(role => { + it(`returns false for tables with role=${role}`, () => { + const vNode = queryFixture(` + +
foo bar
`); + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + }); + + it(`returns true for tables with an invalid role`, () => { + const vNode = queryFixture(` + +
foo bar
`); + assert.isTrue(rule.matches(vNode.actualNode, vNode)); + }); + + it(`returns true for focusable tables with role=none`, () => { + const vNode = queryFixture(` + +
foo bar
`); + assert.isTrue(rule.matches(vNode.actualNode, vNode)); + }); + + it(`returns true for tables with role=none but with a global ARIA attribute`, () => { + const vNode = + queryFixture(` + +
foo bar
`); + assert.isTrue(rule.matches(vNode.actualNode, vNode)); + }); +}); From 22407d5a89cd7835f505f4ba3f8f53c557e218ad Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Tue, 27 Sep 2022 13:19:15 +0200 Subject: [PATCH 2/3] add aria-hidden test --- test/checks/tables/td-headers-attr.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/checks/tables/td-headers-attr.js b/test/checks/tables/td-headers-attr.js index 83db2b936a..4fe08bc0b6 100644 --- a/test/checks/tables/td-headers-attr.js +++ b/test/checks/tables/td-headers-attr.js @@ -123,4 +123,14 @@ describe('td-headers-attr', function () { var node = fixture.querySelector('table'); assert.isTrue(check.call(checkContext, node)); }); + + it('returns true if td[headers] has aria-hidden=true', function () { + fixtureSetup( + '' + + ' ' + + '
Hello
' + ); + var node = fixture.querySelector('table'); + assert.isTrue(check.call(checkContext, node)); + }); }); From 4f6fe8ea9001c4c04fc9ac80331bdc58e0d65546 Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Wed, 28 Sep 2022 11:20:39 +0200 Subject: [PATCH 3/3] revert hidden cell code --- lib/checks/tables/td-headers-attr-evaluate.js | 3 +- test/checks/tables/td-headers-attr.js | 140 ++++++++---------- .../td-headers-attr/td-headers-attr.html | 5 - .../td-headers-attr/td-headers-attr.json | 2 +- 4 files changed, 66 insertions(+), 84 deletions(-) diff --git a/lib/checks/tables/td-headers-attr-evaluate.js b/lib/checks/tables/td-headers-attr-evaluate.js index caef8f9a3a..e781330794 100644 --- a/lib/checks/tables/td-headers-attr-evaluate.js +++ b/lib/checks/tables/td-headers-attr-evaluate.js @@ -1,5 +1,4 @@ import { tokenList } from '../../core/utils'; -import { isVisibleForScreenreader } from '../../commons/dom'; function tdHeadersAttrEvaluate(node) { const cells = []; @@ -25,7 +24,7 @@ function tdHeadersAttrEvaluate(node) { let isSelf = false; let notOfTable = false; - if (!cell.hasAttribute('headers') || !isVisibleForScreenreader(cell)) { + if (!cell.hasAttribute('headers')) { return; } diff --git a/test/checks/tables/td-headers-attr.js b/test/checks/tables/td-headers-attr.js index 4fe08bc0b6..8df3cc03e3 100644 --- a/test/checks/tables/td-headers-attr.js +++ b/test/checks/tables/td-headers-attr.js @@ -3,134 +3,122 @@ describe('td-headers-attr', function () { var fixture = document.getElementById('fixture'); var checkContext = axe.testUtils.MockCheckContext(); - var fixtureSetup = axe.testUtils.fixtureSetup; - var check = axe.testUtils.getCheckEvaluate('td-headers-attr'); afterEach(function () { + fixture.innerHTML = ''; checkContext.reset(); }); it('returns true no headers attribute is present', function () { - fixtureSetup( + fixture.innerHTML = '' + - ' ' + - ' ' + - '
hi hello
hi hello
' - ); + ' hi hello ' + + ' hi hello ' + + ''; var node = fixture.querySelector('table'); - assert.isTrue(check.call(checkContext, node)); + assert.isTrue( + axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) + ); }); it('returns true if a valid header is present', function () { - fixtureSetup( + fixture.innerHTML = '' + - ' ' + - ' ' + - '
hello
goodbye
' - ); + ' hello ' + + ' goodbye ' + + ''; var node = fixture.querySelector('table'); - assert.isTrue(check.call(checkContext, node)); + assert.isTrue( + axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) + ); }); it('returns true if multiple valid headers are present', function () { - fixtureSetup( + fixture.innerHTML = '' + - ' ' + - ' ' + - '
hello hello
goodbye
' - ); + ' hello hello ' + + ' goodbye ' + + ''; var node = fixture.querySelector('table'); - assert.isTrue(check.call(checkContext, node)); + assert.isTrue( + axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) + ); }); it('returns true with an empty header', function () { - fixtureSetup( + fixture.innerHTML = '' + - ' ' + - ' ' + - '
goodbye
' - ); + ' ' + + ' goodbye ' + + ''; var node = fixture.querySelector('table'); - assert.isTrue(check.call(checkContext, node)); + assert.isTrue( + axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) + ); }); it('returns undefined if headers is empty', function () { - fixtureSetup( + fixture.innerHTML = '' + - ' ' + - ' ' + - '
goodbye
' - ); + ' ' + + ' goodbye ' + + ''; var node = fixture.querySelector('table'); - assert.isUndefined(check.call(checkContext, node)); + assert.isUndefined( + axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) + ); }); it('returns false if the header is a table cell', function () { var node; - fixtureSetup( + fixture.innerHTML = '' + - ' ' + - ' ' + - '
hello
goodbye
' - ); + ' hello ' + + ' goodbye ' + + ''; node = fixture.querySelector('table'); - assert.isFalse(check.call(checkContext, node)); + assert.isFalse( + axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) + ); - fixtureSetup( + fixture.innerHTML = 'hello' + - '' + - ' ' + - ' ' + - '
goodbye
' - ); + '' + + ' ' + + ' ' + + '
goodbye
'; node = fixture.querySelector('table'); - assert.isFalse(check.call(checkContext, node)); + assert.isFalse( + axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) + ); - fixtureSetup( + fixture.innerHTML = '' + - ' ' + - ' ' + - '
hello
goodbye
' - ); + ' hello ' + + ' goodbye ' + + ''; node = fixture.querySelector('table'); - assert.isFalse(check.call(checkContext, node)); + assert.isFalse( + axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) + ); }); it('returns false if the header refers to the same cell', function () { - fixtureSetup( + fixture.innerHTML = '' + - ' ' + - ' ' + - '
hello
goodbye
' - ); + ' hello ' + + ' goodbye ' + + ''; var node = fixture.querySelector('table'); - assert.isFalse(check.call(checkContext, node)); - }); - - it('returns true if td[headers] is hidden', function () { - fixtureSetup( - '' + - ' ' + - '
Hello
' + assert.isFalse( + axe.testUtils.getCheckEvaluate('td-headers-attr').call(checkContext, node) ); - var node = fixture.querySelector('table'); - assert.isTrue(check.call(checkContext, node)); - }); - - it('returns true if td[headers] has aria-hidden=true', function () { - fixtureSetup( - '' + - ' ' + - '
Hello
' - ); - var node = fixture.querySelector('table'); - assert.isTrue(check.call(checkContext, node)); }); }); diff --git a/test/integration/rules/td-headers-attr/td-headers-attr.html b/test/integration/rules/td-headers-attr/td-headers-attr.html index 64fc6cb33c..c0afe4060a 100644 --- a/test/integration/rules/td-headers-attr/td-headers-attr.html +++ b/test/integration/rules/td-headers-attr/td-headers-attr.html @@ -14,11 +14,6 @@ World - - - -
Hello
- diff --git a/test/integration/rules/td-headers-attr/td-headers-attr.json b/test/integration/rules/td-headers-attr/td-headers-attr.json index e687869b6b..cda7bf99cb 100644 --- a/test/integration/rules/td-headers-attr/td-headers-attr.json +++ b/test/integration/rules/td-headers-attr/td-headers-attr.json @@ -2,5 +2,5 @@ "description": "td-headers-attr test", "rule": "td-headers-attr", "violations": [["#fail1"], ["#fail2"], ["#fail3"]], - "passes": [["#pass1"], ["#pass2"], ["#pass3"], ["#pass4"]] + "passes": [["#pass1"], ["#pass2"], ["#pass3"]] }
Hello World