From b971caf3eea03170c0710c5b00272fe13f65e825 Mon Sep 17 00:00:00 2001 From: Dan Bowling Date: Tue, 6 Dec 2022 11:46:52 -0700 Subject: [PATCH] fix(get-role): handle presentation role inheritance for vnodes with no parent (#3801) --- lib/commons/aria/get-role.js | 4 ++ lib/rules/empty-table-header.json | 2 +- test/commons/aria/get-role.js | 22 ++++++ .../empty-table-header.html | 6 ++ .../virtual-rules/empty-table-header.js | 72 +++++++++++++++++-- 5 files changed, 98 insertions(+), 8 deletions(-) diff --git a/lib/commons/aria/get-role.js b/lib/commons/aria/get-role.js index 7f6d6556ae..fb5fc7347f 100644 --- a/lib/commons/aria/get-role.js +++ b/lib/commons/aria/get-role.js @@ -54,6 +54,10 @@ function getInheritedRole(vNode, explicitRoleOptions) { // if we can't look at the parent then we can't know if the node // inherits the presentational role or not if (!vNode.parent) { + if (!vNode.actualNode) { + return null; + } + throw new ReferenceError( 'Cannot determine role presentational inheritance of a required parent outside the current scope.' ); diff --git a/lib/rules/empty-table-header.json b/lib/rules/empty-table-header.json index 5989478377..49ff72909e 100644 --- a/lib/rules/empty-table-header.json +++ b/lib/rules/empty-table-header.json @@ -1,6 +1,6 @@ { "id": "empty-table-header", - "selector": "th, [role=\"rowheader\"], [role=\"columnheader\"]", + "selector": "th:not([role]), [role=\"rowheader\"], [role=\"columnheader\"]", "tags": ["cat.name-role-value", "best-practice"], "metadata": { "description": "Ensures table headers have discernible text", diff --git a/test/commons/aria/get-role.js b/test/commons/aria/get-role.js index f0436fe9a0..340c90a88b 100644 --- a/test/commons/aria/get-role.js +++ b/test/commons/aria/get-role.js @@ -412,4 +412,26 @@ describe('aria.getRole', function () { assert.isNull(aria.getRole(node, { noPresentational: true })); }); }); + + describe('SerialVirtualNode', function () { + it('works with the SerialVirtualNode', function () { + var vNode = new axe.SerialVirtualNode({ + nodeName: 'div', + attributes: { + role: 'button' + } + }); + assert.equal(aria.getRole(vNode), 'button'); + }); + + it('does not throw for missing parent in presentational role inheritance', function () { + var vNode = new axe.SerialVirtualNode({ + nodeName: 'li' + }); + + assert.doesNotThrow(function () { + assert.equal(aria.getRole(vNode), 'listitem'); + }); + }); + }); }); diff --git a/test/integration/rules/empty-table-header/empty-table-header.html b/test/integration/rules/empty-table-header/empty-table-header.html index 24b7200d88..bcdb11e18e 100644 --- a/test/integration/rules/empty-table-header/empty-table-header.html +++ b/test/integration/rules/empty-table-header/empty-table-header.html @@ -45,5 +45,11 @@ + + + + + +
rowheader with a role
diff --git a/test/integration/virtual-rules/empty-table-header.js b/test/integration/virtual-rules/empty-table-header.js index f08b2c4140..407c119c77 100644 --- a/test/integration/virtual-rules/empty-table-header.js +++ b/test/integration/virtual-rules/empty-table-header.js @@ -1,5 +1,5 @@ describe('empty-table-header virtual-rule', function () { - it('should incomplete when children are missing', function () { + it('should fail when children contain no visible text', function () { var thNode = new axe.SerialVirtualNode({ nodeName: 'th' }); @@ -7,11 +7,55 @@ describe('empty-table-header virtual-rule', function () { var results = axe.runVirtualRule('empty-table-header', thNode); + assert.lengthOf(results.passes, 0); + assert.lengthOf(results.violations, 1); + assert.lengthOf(results.incomplete, 0); + }); + + it('should incomplete when children are missing', function () { + var thNode = new axe.SerialVirtualNode({ + nodeName: 'th' + }); + + var results = axe.runVirtualRule('empty-table-header', thNode); + assert.lengthOf(results.passes, 0); assert.lengthOf(results.violations, 0); assert.lengthOf(results.incomplete, 1); }); + it('should fail for role=rowheader', function () { + var vNode = new axe.SerialVirtualNode({ + nodeName: 'div', + attributes: { + role: 'rowheader' + } + }); + vNode.children = []; + + var results = axe.runVirtualRule('empty-table-header', vNode); + + assert.lengthOf(results.passes, 0); + assert.lengthOf(results.violations, 1); + assert.lengthOf(results.incomplete, 0); + }); + + it('should fail for role=columnheader', function () { + var vNode = new axe.SerialVirtualNode({ + nodeName: 'div', + attributes: { + role: 'columnheader' + } + }); + vNode.children = []; + + var results = axe.runVirtualRule('empty-table-header', vNode); + + assert.lengthOf(results.passes, 0); + assert.lengthOf(results.violations, 1); + assert.lengthOf(results.incomplete, 0); + }); + it('should pass with a table header', function () { var tableNode = new axe.SerialVirtualNode({ nodeName: 'table' @@ -137,19 +181,33 @@ describe('empty-table-header virtual-rule', function () { assert.lengthOf(results.incomplete, 0); }); - it('should fail if table header has no child nodes', function () { - var node = new axe.SerialVirtualNode({ + it('should be inapplicable when the th has role of cell', function () { + var table = new axe.SerialVirtualNode({ + nodeName: 'table' + }); + + var tr = new axe.SerialVirtualNode({ + nodeName: 'tr' + }); + + var th = new axe.SerialVirtualNode({ nodeName: 'th', attributes: { - role: 'rowheader' + role: 'cell' } }); - node.children = []; - var results = axe.runVirtualRule('empty-table-header', node); + tr.children = [th]; + tr.parent = table; + th.parent = tr; + th.children = []; + table.children = [tr]; + + var results = axe.runVirtualRule('empty-table-header', th); assert.lengthOf(results.passes, 0); - assert.lengthOf(results.violations, 1); + assert.lengthOf(results.violations, 0); assert.lengthOf(results.incomplete, 0); + assert.lengthOf(results.inapplicable, 1); }); });