Skip to content

Commit

Permalink
fix(heading-order): use aria-level on headings in addition to role=he…
Browse files Browse the repository at this point in the history
…ader elements (#3028)

* use aria-level on headings in addition to role=header elements

* account for more cases

* use heading level if aria-level is invalid on a hn tag

* remove console log

Co-authored-by: Wilco Fiers <WilcoFiers@users.noreply.github.com>

* update comment

Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com>

* remove comment

Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com>

* allow heading levels above 6

Co-authored-by: Wilco Fiers <WilcoFiers@users.noreply.github.com>
Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com>
  • Loading branch information
3 people committed Jun 25, 2021
1 parent f478bab commit caccd38
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 20 deletions.
47 changes: 31 additions & 16 deletions lib/checks/navigation/heading-order-evaluate.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,42 @@
import cache from '../../core/base/cache';
import { querySelectorAllFilter, getAncestry } from '../../core/utils';
import { isVisible } from '../../commons/dom';
import { getRole } from '../../commons/aria';

function getLevel(vNode) {
const role = vNode.attr('role');
if (role && role.includes('heading')) {
const ariaHeadingLevel = vNode.attr('aria-level');
const level = parseInt(ariaHeadingLevel, 10);

// default aria-level for a heading is 2 if it is
// not set or set to an incorrect value
// @see https://www.w3.org/TR/wai-aria-1.1/#heading
if (isNaN(level) || level < 1 || level > 6) {
return 2;
}
const role = getRole(vNode);
const headingRole = role && role.includes('heading');
const ariaHeadingLevel = vNode.attr('aria-level');
const ariaLevel = parseInt(ariaHeadingLevel, 10);

const [, headingLevel] = vNode.props.nodeName.match(/h(\d)/) || [];

if (!headingRole) {
return -1;
}

return level;
if (headingLevel && !ariaHeadingLevel) {
return parseInt(headingLevel, 10);
}

const headingLevel = vNode.props.nodeName.match(/h(\d)/);
if (headingLevel) {
return parseInt(headingLevel[1], 10);
/*
* default aria-level for a role=heading is 2 if it is
* not set or set to an incorrect value.
* default aria-level for a heading element is the
* heading level.
* note that NVDA and VO allow any positive level
* @see https://www.w3.org/TR/wai-aria-1.1/#heading
* @see https://codepen.io/straker/pen/jOBjNNe
*/
if (isNaN(ariaLevel) || ariaLevel < 1) {
if (headingLevel) {
return parseInt(headingLevel, 10);
}
return 2;
}

if (ariaLevel) {
return ariaLevel;
}

return -1;
Expand Down Expand Up @@ -49,7 +65,6 @@ function headingOrderEvaluate() {
level: getLevel(vNode)
};
});

this.data({ headingOrder });
cache.set('headingOrder', vNodes);
return true;
Expand Down
101 changes: 97 additions & 4 deletions test/checks/navigation/heading-order.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('heading-order', function() {

it('should handle incorrect aria-level values', function() {
var vNode = queryFixture(
'<div role="heading" aria-level="-1" id="target">One</div><div role="heading" aria-level="12">Two</div><div role="heading">Three</div>'
'<div role="heading" aria-level="-1" id="target">One</div><div role="heading">Two</div>'
);
assert.isTrue(
axe.testUtils
Expand All @@ -49,10 +49,25 @@ describe('heading-order', function() {
{
ancestry: ['html > body > div:nth-child(1) > div:nth-child(2)'],
level: 2
},
}
]
});
});

it('should allow high aria-level values', function() {
var vNode = queryFixture(
'<div role="heading" aria-level="12" id="target">One</div>'
);
assert.isTrue(
axe.testUtils
.getCheckEvaluate('heading-order')
.call(checkContext, null, {}, vNode, {})
);
assert.deepEqual(checkContext._data, {
headingOrder: [
{
ancestry: ['html > body > div:nth-child(1) > div:nth-child(3)'],
level: 2
ancestry: ['html > body > div:nth-child(1) > div'],
level: 12
}
]
});
Expand All @@ -79,6 +94,84 @@ describe('heading-order', function() {
});
});

it('should allow aria-level to override semantic level for hn tags and return true', function() {
var vNode = queryFixture(
'<h1 aria-level="2" id="target">Two</h1><h3 aria-level="4">Four</h3>'
);
assert.isTrue(
axe.testUtils
.getCheckEvaluate('heading-order')
.call(checkContext, null, {}, vNode, {})
);
assert.deepEqual(checkContext._data, {
headingOrder: [
{
ancestry: ['html > body > div:nth-child(1) > h1:nth-child(1)'],
level: 2
},
{
ancestry: ['html > body > div:nth-child(1) > h3:nth-child(2)'],
level: 4
}
]
});
});

it('should ignore aria-level on iframe when not used with role=heading', function() {
var vNode = queryFixture('<iframe aria-level="2"></iframe>');
axe.testUtils
.getCheckEvaluate('heading-order')
.call(checkContext, null, {}, vNode, { initiator: true });
assert.deepEqual(checkContext._data, {
headingOrder: [
{
ancestry: ['html > body > div:nth-child(1) > iframe'],
level: -1
}
]
});
});

it('should correctly give level on hn tag with role=heading', function() {
var vNode = queryFixture(
'<h1 role="heading" id="target">One</h1><h3 role="heading">Three</h3>'
);
assert.isTrue(
axe.testUtils
.getCheckEvaluate('heading-order')
.call(checkContext, null, {}, vNode, {})
);
assert.deepEqual(checkContext._data, {
headingOrder: [
{
ancestry: ['html > body > div:nth-child(1) > h1:nth-child(1)'],
level: 1
},
{
ancestry: ['html > body > div:nth-child(1) > h3:nth-child(2)'],
level: 3
}
]
});
});

it('should return the heading level when an hn tag has an invalid aria-level', function() {
var vNode = queryFixture('<h1 aria-level="-1" id="target">One</h1>');
assert.isTrue(
axe.testUtils
.getCheckEvaluate('heading-order')
.call(checkContext, null, {}, vNode, {})
);
assert.deepEqual(checkContext._data, {
headingOrder: [
{
ancestry: ['html > body > div:nth-child(1) > h1'],
level: 1
}
]
});
});

it('should store the location of iframes', function() {
var vNode = queryFixture(
'<h1 id="target">One</h1><iframe></iframe><h3>Three</h3>'
Expand Down

0 comments on commit caccd38

Please sign in to comment.