Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(empty-heading): use virtual node #3582

Merged
merged 27 commits into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
48d7e02
refactor(empty-heading): use virtual node
dbowling Aug 2, 2022
0d4b387
wip - check
dbowling Aug 2, 2022
b1285e3
wip
dbowling Aug 2, 2022
23b705c
wip
dbowling Aug 2, 2022
e4d58a0
wip
dbowling Aug 3, 2022
c98bc15
Merge branch 'develop' into chore/issue-3473/empty-heading
dbowling Aug 3, 2022
c64ab3c
Merge branch 'develop' into chore/issue-3473/empty-heading
dbowling Aug 3, 2022
e41211e
use virtual node instead of actual
dbowling Aug 3, 2022
6738d78
Merge branch 'chore/issue-3473/empty-heading' of github.com:dequelabs…
dbowling Aug 3, 2022
980c002
refactor to vnodes
dbowling Aug 3, 2022
9b69990
heading-matches as virtual node
dbowling Aug 3, 2022
89a20fe
add virtual rule tests
dbowling Aug 3, 2022
be37d06
complete jsdoc description
dbowling Aug 4, 2022
bdc9a2b
add failing/stubbed out tests
dbowling Aug 4, 2022
dd684ff
pass for title
dbowling Aug 4, 2022
260ef5d
test for implicit and explicit roles
dbowling Aug 4, 2022
8ad90d0
fix typo
dbowling Aug 4, 2022
eb6833a
remove debug
dbowling Aug 4, 2022
fb449cf
rm: should fail with no visible text (impossible)
dbowling Aug 4, 2022
44a8499
fix aria-label test
dbowling Aug 4, 2022
b645ae6
fix title test
dbowling Aug 4, 2022
407f850
clean up describes
dbowling Aug 5, 2022
9a17afc
Merge branch 'develop' into chore/issue-3473/empty-heading
dbowling Aug 5, 2022
8463715
add presentation conflict resolution failing test
dbowling Aug 5, 2022
a30d74b
add conflict resolution unit test
dbowling Aug 5, 2022
a2c28e2
set children in virtual rule to not incomplete
dbowling Aug 5, 2022
8be5db2
fix: typo in test
dbowling Aug 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions lib/checks/generic/has-text-content-evaluate.js
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion lib/commons/text/accessible-text-virtual.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 3 additions & 17 deletions lib/rules/heading-matches.js
Original file line number Diff line number Diff line change
@@ -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;
24 changes: 10 additions & 14 deletions test/checks/shared/has-visible-text.js
Original file line number Diff line number Diff line change
@@ -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('<p id="target"></p>');
assert.isFalse(
axe.testUtils
Expand All @@ -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(
'<p id="target"><span style="display:none">hello!</span></p>'
);
Expand All @@ -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('<p id="target">hello!</p>');
assert.isTrue(
axe.testUtils
Expand All @@ -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'
});
Expand All @@ -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'
});
Expand All @@ -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'
});
Expand All @@ -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'
});
Expand Down
149 changes: 149 additions & 0 deletions test/integration/virtual-rules/empty-heading.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
83 changes: 29 additions & 54 deletions test/rule-matches/heading-matches.js
Original file line number Diff line number Diff line change
@@ -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('<div></div>');
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('<div role="heading" id="target"></div>');
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('<h' + i + ' id="target"></h' + i + '>');
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('<h1 role="banner" id="target"></h1>');
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('<h1 role="bruce" id="target"></h1>');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh, good ol' role=bruce

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('<h1 role="widget" id="target"></h1>');
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('<h1 aria-label="" role="none" id="target"></h1>');
assert.isTrue(rule.matches(null, vNode));
});
});