diff --git a/lib/checks/generic/has-text-content-evaluate.js b/lib/checks/generic/has-text-content-evaluate.js index 0d1ba74fb8..a3d28d5a7d 100644 --- a/lib/checks/generic/has-text-content-evaluate.js +++ b/lib/checks/generic/has-text-content-evaluate.js @@ -1,11 +1,9 @@ import { sanitize, subtreeText } from '../../commons/text'; -function hasTextContentEvaluate(node, options, virtualNode) { +export default function hasTextContentEvaluate(node, options, virtualNode) { try { return sanitize(subtreeText(virtualNode)) !== ''; } catch (e) { return undefined; } } - -export default hasTextContentEvaluate; diff --git a/lib/commons/text/accessible-text-virtual.js b/lib/commons/text/accessible-text-virtual.js index 09a00e4d77..903adb0dcf 100644 --- a/lib/commons/text/accessible-text-virtual.js +++ b/lib/commons/text/accessible-text-virtual.js @@ -74,7 +74,7 @@ function textNodeValue(virtualNode) { } /** - * Check if the + * Check if the VirtualNode should be ignored because it is not visible or hidden should be included * @param {VirtualNode} element * @param {Object} context * @property {VirtualNode[]} processed diff --git a/lib/rules/heading-matches.js b/lib/rules/heading-matches.js index fcfa3f670c..7a5d519361 100644 --- a/lib/rules/heading-matches.js +++ b/lib/rules/heading-matches.js @@ -1,19 +1,5 @@ -function headingMatches(node) { - // Get all valid roles - let explicitRoles; - if (node.hasAttribute('role')) { - explicitRoles = node - .getAttribute('role') - .split(/\s+/i) - .filter(axe.commons.aria.isValidRole); - } +import { getRole } from '../commons/aria'; - // Check valid roles if there are any, otherwise fall back to the inherited role - if (explicitRoles && explicitRoles.length > 0) { - return explicitRoles.includes('heading'); - } else { - return axe.commons.aria.implicitRole(node) === 'heading'; - } +export default function headingMatches(node, virtualNode) { + return getRole(virtualNode) === 'heading'; } - -export default headingMatches; diff --git a/test/checks/shared/has-visible-text.js b/test/checks/shared/has-visible-text.js index 77b1886742..fedca81cb4 100644 --- a/test/checks/shared/has-visible-text.js +++ b/test/checks/shared/has-visible-text.js @@ -1,18 +1,14 @@ -describe('has-visible-text', function() { +describe('has-visible-text', function () { 'use strict'; - var fixture = document.getElementById('fixture'); var checkSetup = axe.testUtils.checkSetup; - var checkContext = axe.testUtils.MockCheckContext(); - afterEach(function() { - fixture.innerHTML = ''; - axe._tree = undefined; + afterEach(function () { checkContext.reset(); }); - it('should return false if there is no visible text', function() { + it('should return false if there is no visible text', function () { var params = checkSetup('

'); assert.isFalse( axe.testUtils @@ -21,7 +17,7 @@ describe('has-visible-text', function() { ); }); - it('should return false if there is text, but its hidden', function() { + it('should return false if there is text, but its hidden', function () { var params = checkSetup( '

hello!

' ); @@ -32,7 +28,7 @@ describe('has-visible-text', function() { ); }); - it('should return true if there is visible text', function() { + it('should return true if there is visible text', function () { var params = checkSetup('

hello!

'); assert.isTrue( axe.testUtils @@ -41,8 +37,8 @@ describe('has-visible-text', function() { ); }); - describe('SerialVirtualNode', function() { - it('should return false if element is not named from contents', function() { + describe('SerialVirtualNode', function () { + it('should return false if element is not named from contents', function () { var node = new axe.SerialVirtualNode({ nodeName: 'article' }); @@ -52,7 +48,7 @@ describe('has-visible-text', function() { ); }); - it('should return incomplete if no other properties are set', function() { + it('should return incomplete if no other properties are set', function () { var node = new axe.SerialVirtualNode({ nodeName: 'button' }); @@ -62,7 +58,7 @@ describe('has-visible-text', function() { ); }); - it('should return false if there is no visible text', function() { + it('should return false if there is no visible text', function () { var node = new axe.SerialVirtualNode({ nodeName: 'button' }); @@ -73,7 +69,7 @@ describe('has-visible-text', function() { ); }); - it('should return true if there is visible text', function() { + it('should return true if there is visible text', function () { var node = new axe.SerialVirtualNode({ nodeName: 'p' }); diff --git a/test/integration/virtual-rules/empty-heading.js b/test/integration/virtual-rules/empty-heading.js new file mode 100644 index 0000000000..544a26dea3 --- /dev/null +++ b/test/integration/virtual-rules/empty-heading.js @@ -0,0 +1,149 @@ +describe('empty-heading virtual-rule', function () { + it('should pass with visible text', function () { + var node = new axe.SerialVirtualNode({ + nodeName: 'h1', + attributes: {} + }); + var child = new axe.SerialVirtualNode({ + nodeName: '#text', + nodeType: 3, + nodeValue: 'OK', + attributes: {} + }); + + node.children = [child]; + + var results = axe.runVirtualRule('empty-heading', node); + + assert.lengthOf(results.violations, 0); + assert.lengthOf(results.incomplete, 0); + assert.lengthOf(results.passes, 1); + }); + + it('should incomplete if no other properties are set', function () { + var node = new axe.SerialVirtualNode({ + nodeName: 'h1' + }); + + var results = axe.runVirtualRule('empty-heading', node); + + assert.lengthOf(results.violations, 0); + assert.lengthOf(results.incomplete, 1); + assert.lengthOf(results.passes, 0); + }); + + it('should pass for title', function () { + var results = axe.runVirtualRule('empty-heading', { + nodeName: 'h1', + attributes: { + title: 'it has a title' + } + }); + + assert.lengthOf(results.passes, 1); + assert.lengthOf(results.violations, 0); + assert.lengthOf(results.incomplete, 0); + }); + + it('should pass on explicit role', function () { + var results = axe.runVirtualRule('empty-heading', { + nodeName: 'span', + attributes: { + role: 'heading', + title: 'foobar' + } + }); + + assert.lengthOf(results.passes, 1); + assert.lengthOf(results.violations, 0); + assert.lengthOf(results.incomplete, 0); + }); + + it('should pass on implicit role', function () { + var results = axe.runVirtualRule('empty-heading', { + nodeName: 'h1', + attributes: { + title: 'foobar' + } + }); + + assert.lengthOf(results.passes, 1); + assert.lengthOf(results.violations, 0); + assert.lengthOf(results.incomplete, 0); + }); + + it('should pass for aria-label', function () { + var results = axe.runVirtualRule('empty-heading', { + nodeName: 'h1', + attributes: { + 'aria-label': 'foobar' + } + }); + + assert.lengthOf(results.passes, 1); + assert.lengthOf(results.violations, 0); + assert.lengthOf(results.incomplete, 0); + }); + + it('should fail when aria-label is empty', function () { + var node = new axe.SerialVirtualNode({ + nodeName: 'h1', + attributes: { + 'aria-label': '' + } + }); + node.children = []; + + var results = axe.runVirtualRule('empty-heading', node); + + assert.lengthOf(results.passes, 0); + assert.lengthOf(results.violations, 1); + assert.lengthOf(results.incomplete, 0); + }); + + it('should incomplete for aria-labelledby', function () { + var results = axe.runVirtualRule('empty-heading', { + nodeName: 'h1', + attributes: { + 'aria-labelledby': 'foobar' + } + }); + + assert.lengthOf(results.passes, 0); + assert.lengthOf(results.violations, 0); + assert.lengthOf(results.incomplete, 1); + }); + + it('should fail when title is empty', function () { + var node = new axe.SerialVirtualNode({ + nodeName: 'h1', + attributes: { + title: '' + } + }); + node.children = []; + + var results = axe.runVirtualRule('empty-heading', node); + + assert.lengthOf(results.passes, 0); + assert.lengthOf(results.violations, 1); + assert.lengthOf(results.incomplete, 0); + }); + + it('should fail in order to account for presentation conflict resolution', function () { + var node = new axe.SerialVirtualNode({ + nodeName: 'h1', + attributes: { + role: 'none', + 'aria-label': '' + } + }); + node.children = []; + + var results = axe.runVirtualRule('empty-heading', node); + + assert.lengthOf(results.passes, 0); + assert.lengthOf(results.violations, 1); + assert.lengthOf(results.incomplete, 0); + }); +}); diff --git a/test/rule-matches/heading-matches.js b/test/rule-matches/heading-matches.js index e5ce5ff910..4939f16b9f 100644 --- a/test/rule-matches/heading-matches.js +++ b/test/rule-matches/heading-matches.js @@ -1,76 +1,51 @@ -describe('heading-matches', function() { +describe('heading-matches', function () { 'use strict'; - - var fixture = document.getElementById('fixture'); - var flatTreeSetup = axe.testUtils.flatTreeSetup; + var queryFixture = axe.testUtils.queryFixture; + var fixtureSetup = axe.testUtils.fixtureSetup; var rule; - beforeEach(function() { + beforeEach(function () { rule = axe.utils.getRule('empty-heading'); }); - afterEach(function() { - fixture.innerHTML = ''; - }); - - it('is a function', function() { + it('is a function', function () { assert.isFunction(rule.matches); }); - it('should return false on elements that are not headings', function() { - var div = document.createElement('div'); - fixture.appendChild(div); - flatTreeSetup(fixture); - assert.isFalse(rule.matches(div)); + it('should return false on elements that are not headings', function () { + var vNode = fixtureSetup('
'); + assert.isFalse(rule.matches(null, vNode)); }); - it('should return true on elements with "heading" in the role', function() { - var div = document.createElement('div'); - div.setAttribute('role', 'heading'); - fixture.appendChild(div); - flatTreeSetup(fixture); - assert.isTrue(rule.matches(div)); - - div.setAttribute('role', 'slider heading'); - assert.isTrue(rule.matches(div)); + it('should return true on elements with role="heading"', function () { + var vNode = queryFixture('
'); + assert.isTrue(rule.matches(null, vNode)); }); - it('should return true on regular headings without roles', function() { - var h1 = document.createElement('h1'); - var h2 = document.createElement('h2'); - var h3 = document.createElement('h3'); - - fixture.appendChild(h1); - fixture.appendChild(h2); - fixture.appendChild(h3); + it('should return true on regular headings without roles', function () { + for (var i = 1; i <= 6; i++) { + var vNode = queryFixture(''); + assert.isTrue(rule.matches(null, vNode)); + } + }); - flatTreeSetup(fixture); - assert.isTrue(rule.matches(h1)); - assert.isTrue(rule.matches(h2)); - assert.isTrue(rule.matches(h3)); + it('should return false on headings with their role changes', function () { + var vNode = queryFixture('

'); + assert.isFalse(rule.matches(null, vNode)); }); - it('should return false on headings with their role changes', function() { - var h1 = document.createElement('h1'); - h1.setAttribute('role', 'banner'); - fixture.appendChild(h1); - flatTreeSetup(fixture); - assert.isFalse(rule.matches(h1)); + it('should return true on headings with their role changes to an invalid role', function () { + var vNode = queryFixture('

'); + assert.isTrue(rule.matches(null, vNode)); }); - it('should return true on headings with their role changes to an invalid role', function() { - var h1 = document.createElement('h1'); - h1.setAttribute('role', 'bruce'); - fixture.appendChild(h1); - flatTreeSetup(fixture); - assert.isTrue(rule.matches(h1)); + it('should return true on headings with their role changes to an abstract role', function () { + var vNode = queryFixture('

'); + assert.isTrue(rule.matches(null, vNode)); }); - it('should return true on headings with their role changes to an abstract role', function() { - var h1 = document.createElement('h1'); - h1.setAttribute('role', 'widget'); - fixture.appendChild(h1); - flatTreeSetup(fixture); - assert.isTrue(rule.matches(h1)); + it('should return true on headings with explicit role="none" and an empty aria-label to account for presentation conflict resolution', function () { + var vNode = queryFixture('

'); + assert.isTrue(rule.matches(null, vNode)); }); });