From 1ecf56b57dde6120e68749add6336f18034367b9 Mon Sep 17 00:00:00 2001 From: Dan Bowling Date: Mon, 28 Nov 2022 15:31:34 -0700 Subject: [PATCH 1/3] fix(get-role): handle presentation role inheritance for vnodes with no parent Closes issue: #3788 --- 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 | 62 ++++++++++++++----- 5 files changed, 78 insertions(+), 18 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..5d0db656c6 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' @@ -136,20 +180,4 @@ describe('empty-table-header virtual-rule', function () { assert.lengthOf(results.violations, 0); assert.lengthOf(results.incomplete, 0); }); - - it('should fail if table header has no child nodes', function () { - var node = new axe.SerialVirtualNode({ - nodeName: 'th', - attributes: { - role: 'rowheader' - } - }); - node.children = []; - - var results = axe.runVirtualRule('empty-table-header', node); - - assert.lengthOf(results.passes, 0); - assert.lengthOf(results.violations, 1); - assert.lengthOf(results.incomplete, 0); - }); }); From 12e3f9f86d4a826c3303d0bd72136cd64f9d0e77 Mon Sep 17 00:00:00 2001 From: Dan Bowling Date: Tue, 6 Dec 2022 10:32:29 -0700 Subject: [PATCH 2/3] add test case for th role of cell --- .../virtual-rules/empty-table-header.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/integration/virtual-rules/empty-table-header.js b/test/integration/virtual-rules/empty-table-header.js index 5d0db656c6..407c119c77 100644 --- a/test/integration/virtual-rules/empty-table-header.js +++ b/test/integration/virtual-rules/empty-table-header.js @@ -180,4 +180,34 @@ describe('empty-table-header virtual-rule', function () { assert.lengthOf(results.violations, 0); assert.lengthOf(results.incomplete, 0); }); + + 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: 'cell' + } + }); + + 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, 0); + assert.lengthOf(results.incomplete, 0); + assert.lengthOf(results.inapplicable, 1); + }); }); From 62695da387e2d582ed9200260e19689d0adc66fd Mon Sep 17 00:00:00 2001 From: dbowling Date: Tue, 6 Dec 2022 17:34:02 +0000 Subject: [PATCH 3/3] :robot: Automated formatting fixes --- lib/checks/label/label-content-name-mismatch-evaluate.js | 3 ++- lib/commons/text/accessible-text-virtual.js | 3 ++- .../rules/aria-allowed-role/aria-allowed-role.html | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/checks/label/label-content-name-mismatch-evaluate.js b/lib/checks/label/label-content-name-mismatch-evaluate.js index 5092d7a763..5686c6093f 100644 --- a/lib/checks/label/label-content-name-mismatch-evaluate.js +++ b/lib/checks/label/label-content-name-mismatch-evaluate.js @@ -39,7 +39,8 @@ function curateString(str) { function labelContentNameMismatchEvaluate(node, options, virtualNode) { const pixelThreshold = options?.pixelThreshold; - const occurrenceThreshold = options?.occurrenceThreshold ?? options?.occuranceThreshold; + const occurrenceThreshold = + options?.occurrenceThreshold ?? options?.occuranceThreshold; const accText = accessibleText(node).toLowerCase(); if (isHumanInterpretable(accText) < 1) { diff --git a/lib/commons/text/accessible-text-virtual.js b/lib/commons/text/accessible-text-virtual.js index 44453bb063..4acecb0c25 100644 --- a/lib/commons/text/accessible-text-virtual.js +++ b/lib/commons/text/accessible-text-virtual.js @@ -105,7 +105,8 @@ function shouldIgnoreHidden(virtualNode, context) { */ function shouldIgnoreIconLigature(virtualNode, context) { const { ignoreIconLigature, pixelThreshold } = context; - const occurrenceThreshold = context.occurrenceThreshold ?? context.occuranceThreshold; + const occurrenceThreshold = + context.occurrenceThreshold ?? context.occuranceThreshold; if (virtualNode.props.nodeType !== 3 || !ignoreIconLigature) { return false; } diff --git a/test/integration/rules/aria-allowed-role/aria-allowed-role.html b/test/integration/rules/aria-allowed-role/aria-allowed-role.html index 184136f587..1760747d16 100644 --- a/test/integration/rules/aria-allowed-role/aria-allowed-role.html +++ b/test/integration/rules/aria-allowed-role/aria-allowed-role.html @@ -141,7 +141,10 @@

role="combobox" type="button" aria-expanded="true" - id="pass-button-role-combobox">ok + id="pass-button-role-combobox" +> + ok +