diff --git a/lib/checks/tables/td-has-header.js b/lib/checks/tables/td-has-header.js
index cb9c9b70af..83b01179e8 100644
--- a/lib/checks/tables/td-has-header.js
+++ b/lib/checks/tables/td-has-header.js
@@ -9,10 +9,9 @@ cells.forEach((cell) => {
!axe.commons.aria.label(cell)
) {
// Check if it has any headers
- var hasHeaders = tableUtils.getHeaders(cell);
- hasHeaders = hasHeaders.reduce(function (hasHeaders, header) {
- return (hasHeaders || header !== null && !!axe.commons.dom.hasContent(header));
- }, false);
+ const hasHeaders = tableUtils.getHeaders(cell).some(header => {
+ return header !== null && !!axe.commons.dom.hasContent(header);
+ });
// If no headers, put it on the naughty list
if (!hasHeaders) {
diff --git a/lib/commons/table/is-data-cell.js b/lib/commons/table/is-data-cell.js
index 0a0b97e3f0..76e12c0c88 100644
--- a/lib/commons/table/is-data-cell.js
+++ b/lib/commons/table/is-data-cell.js
@@ -13,5 +13,10 @@ table.isDataCell = function (cell) {
if (!cell.children.length && !cell.textContent.trim()) {
return false;
}
- return cell.nodeName.toUpperCase() === 'TD';
+ const role = cell.getAttribute('role');
+ if (axe.commons.aria.isValidRole(role)) {
+ return ['cell', 'gridcell'].includes(role);
+ } else {
+ return cell.nodeName.toUpperCase() === 'TD';
+ }
};
diff --git a/test/commons/table/is-data-cell.js b/test/commons/table/is-data-cell.js
index d800ccc26b..1e52561916 100644
--- a/test/commons/table/is-data-cell.js
+++ b/test/commons/table/is-data-cell.js
@@ -40,4 +40,42 @@ describe('table.isDataCell', function () {
assert.isFalse(axe.commons.table.isDataCell(target));
});
+ it('should ignore TDs with a valid role other than (grid)cell', function () {
+ fixture.innerHTML = '
' +
+ 'heading |
' +
+ 'heading |
' +
+ 'heading |
' +
+ '
';
+
+ var target1 = $id('target1');
+ var target2 = $id('target2');
+ var target3 = $id('target3');
+ assert.isFalse(axe.commons.table.isDataCell(target1));
+ assert.isFalse(axe.commons.table.isDataCell(target2));
+ assert.isFalse(axe.commons.table.isDataCell(target3));
+ });
+
+ it('should return true for elements with role="(grid)cell"', function () {
+ fixture.innerHTML = '' +
+ 'heading |
' +
+ 'heading |
' +
+ '
';
+
+ var target1 = $id('target1');
+ var target2 = $id('target2');
+ assert.isTrue(axe.commons.table.isDataCell(target1));
+ assert.isTrue(axe.commons.table.isDataCell(target2));
+ });
+
+ it('should ignore invalid roles', function () {
+ fixture.innerHTML = '' +
+ 'heading |
' +
+ 'heading |
' +
+ '
';
+
+ var target1 = $id('target1');
+ var target2 = $id('target2');
+ assert.isTrue(axe.commons.table.isDataCell(target1));
+ assert.isFalse(axe.commons.table.isDataCell(target2));
+ });
});
diff --git a/test/integration/rules/td-has-header/td-has-header.html b/test/integration/rules/td-has-header/td-has-header.html
index 3d7e7e8fd8..c742f61640 100644
--- a/test/integration/rules/td-has-header/td-has-header.html
+++ b/test/integration/rules/td-has-header/td-has-header.html
@@ -27,7 +27,6 @@
AXE | aXe | aXe | aXe |
-
AXE | AXE | AXE | AXE |
AXE | aXe | aXe | aXe |
@@ -35,6 +34,25 @@
AXE | aXe | aXe | aXe |
+
+
+ AXE |
+ AXE |
+ AXE |
+ AXE |
+
+ aXe | aXe | aXe | aXe |
+ aXe | aXe | aXe | aXe |
+ aXe | aXe | aXe | aXe |
+
+
+
+ AXE | aXe | aXe | aXe |
+ AXE | aXe | aXe | aXe |
+ AXE | aXe | aXe | aXe |
+ AXE | aXe | aXe | aXe |
+
+
AXE | AXE | AXE | aXe |
aXe | aXe | aXe | aXe |
diff --git a/test/integration/rules/td-has-header/td-has-header.json b/test/integration/rules/td-has-header/td-has-header.json
index ec81742429..3e50ce53cf 100644
--- a/test/integration/rules/td-has-header/td-has-header.json
+++ b/test/integration/rules/td-has-header/td-has-header.json
@@ -2,5 +2,11 @@
"description": "td-has-header test",
"rule": "td-has-header",
"violations": [["#fail1"], ["#fail2"]],
- "passes": [["#pass1"], ["#pass2"], ["#pass3"]]
+ "passes": [
+ ["#pass1"],
+ ["#pass2"],
+ ["#pass3"],
+ ["#pass4"],
+ ["#pass5"]
+ ]
}