axe-core
Version:
Accessibility engine for automated Web UI testing
575 lines (467 loc) • 17.1 kB
JavaScript
function createContentGetSelector() {
'use strict';
var group = document.createElement('div');
group.innerHTML = '<label id="mylabel">Label</label><input id="myinput" aria-labelledby="mylabel" type="text" />';
return group;
}
function makeShadowTreeGetSelector(node) {
'use strict';
var root = node.attachShadow({mode: 'open'});
var div = document.createElement('div');
div.className = 'parent';
root.appendChild(div);
div.appendChild(createContentGetSelector());
}
function makeNonunique(fixture) {
'use strict';
var nonUnique = '<div><div><div></div></div></div>';
fixture.innerHTML = '<main>' +
nonUnique + nonUnique + nonUnique +
'<div><div></div></div>';
var node = document.createElement('div');
var parent = fixture.querySelector('div:nth-child(4) > div');
parent.appendChild(node);
return node;
}
function makeNonuniqueLongAttributes(fixture) {
'use strict';
var nonUnique = '<div><div><div></div></div></div>';
fixture.innerHTML = '<main>' +
nonUnique + nonUnique + nonUnique +
'<div><div></div></div>';
var node = document.createElement('div');
node.setAttribute('data-att', 'ddfkjghlkdddfkjghlkdddfkjghlkdddfkjghlkd');
var parent = fixture.querySelector('div:nth-child(4) > div');
parent.appendChild(node);
return node;
}
describe('axe.utils.getSelector', function () {
'use strict';
var fixture = document.getElementById('fixture');
var shadowSupported = axe.testUtils.shadowSupport.v1;
var fixtureSetup = axe.testUtils.fixtureSetup;
afterEach(function () {
fixture.innerHTML = '';
axe._tree = undefined;
axe._selectorData = undefined;
});
it('should be a function', function () {
assert.isFunction(axe.utils.getSelector);
});
it('throws if axe._selectorData is undefined', function () {
assert.throws(function () {
var node = document.createElement('div');
fixture.appendChild(node);
axe.utils.getSelector(node);
});
});
it('should generate a unique CSS selector', function () {
var node = document.createElement('div');
fixtureSetup(node);
var sel = axe.utils.getSelector(node);
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node);
});
it('should still work if an element has nothing but whitespace as a className', function () {
var node = document.createElement('div');
node.className = ' ';
fixtureSetup(node);
var sel = axe.utils.getSelector(node);
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node);
});
it('should handle special characters in IDs', function () {
var node = document.createElement('div');
node.id = 'monkeys#are.animals\\ok';
fixtureSetup(node);
var result = document.querySelectorAll(axe.utils.getSelector(node));
assert.lengthOf(result, 1);
assert.equal(result[0], node);
});
it('should handle special characters in classNames', function () {
var node = document.createElement('div');
node.className = '. bb-required';
fixtureSetup(node);
var result = document.querySelectorAll(axe.utils.getSelector(node));
assert.lengthOf(result, 1);
assert.equal(result[0], node);
});
it('should be able to fall back to positional selectors', function () {
var node, expected;
var nodes = []
for (var i = 0; i < 10; i++) {
node = document.createElement('div');
nodes.push(node);
if (i === 5) {
expected = node;
}
}
fixtureSetup(nodes);
var result = document.querySelectorAll(axe.utils.getSelector(expected));
assert.lengthOf(result, 1);
assert.equal(result[0], expected);
});
it('should use a unique ID', function () {
var node = document.createElement('div');
node.id = 'monkeys';
fixtureSetup(node);
var sel = axe.utils.getSelector(node);
assert.equal(sel, '#monkeys');
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node);
});
it('should not use ids if they are not unique', function () {
var node1 = document.createElement('div');
var node2 = document.createElement('div');
node1.id = 'monkeys';
node2.id = 'monkeys';
fixtureSetup([node1, node2]);
var sel = axe.utils.getSelector(node2);
assert.notInclude(sel, '#monkeys');
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node2);
});
it('should use classes if available and unique', function () {
var node1 = document.createElement('div');
var node2 = document.createElement('div');
node1.className = 'monkeys simian';
node2.className = 'dogs cats';
fixtureSetup([node1, node2]);
var sel = axe.utils.getSelector(node2);
assert.equal(sel, '.dogs');
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node2);
});
it('should use classes if more unique than the tag', function () {
var node1 = document.createElement('p');
var node2 = document.createElement('p');
node1.className = 'monkeys simian cats';
node2.className = 'dogs cats';
fixtureSetup([node1, node2]);
var sel = axe.utils.getSelector(node2);
assert.equal(sel, '.dogs');
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node2);
});
it('should NOT use classes if they are more common than the tag', function () {
var node1 = document.createElement('p');
var node2 = document.createElement('p');
node1.className = 'dogs cats';
node2.className = 'dogs cats';
fixtureSetup([node1, node2]);
var sel = axe.utils.getSelector(node2);
assert.isTrue(sel.indexOf('.dogs') === -1);
assert.isTrue(sel.indexOf('p') === 0);
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node2);
});
it('should use the most unique class', function () {
var node1 = document.createElement('div');
var node2 = document.createElement('div');
node1.className = 'dogs';
node2.className = 'dogs cats';
fixtureSetup([node1, node2]);
var sel = axe.utils.getSelector(node2);
assert.equal(sel, '.cats')
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node2);
});
it('should use the most unique class and not the unique attribute', function () {
var node1 = document.createElement('div');
var node2 = document.createElement('div');
node1.className = 'dogs';
node2.className = 'dogs cats';
node2.setAttribute('data-axe', 'hello');
fixtureSetup([node1, node2]);
var sel = axe.utils.getSelector(node2);
assert.equal(sel, '.cats');
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node2);
});
it('should use only a single unique attribute', function () {
var node1 = document.createElement('div');
var node2 = document.createElement('div');
node1.setAttribute('data-thing', 'hello');
node2.setAttribute('data-thing', 'hello');
node2.setAttribute('data-axe', 'hello');
fixtureSetup([node1, node2]);
var sel = axe.utils.getSelector(node2);
assert.equal(sel, 'div[data-axe="hello"]');
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node2);
});
it('should use three uncommon but not unique features', function () {
var node1 = document.createElement('div');
node1.setAttribute('data-axe', 'hello');
node1.setAttribute('data-thing', 'hello');
node1.className = 'thing';
var node2 = document.createElement('div');
node2.setAttribute('data-axe', 'hello');
node2.setAttribute('data-thing', 'hello');
node2.className = 'thing';
fixtureSetup([node1, node2]);
var sel = axe.utils.getSelector(node2);
var clsIndex = sel.indexOf('.thing');
var attIndex = Math.min(sel.indexOf('[data-axe="hello"]'),
sel.indexOf('[data-thing="hello"]'));
assert.isTrue(clsIndex !== -1);
assert.isTrue(sel.indexOf('[data-axe="hello"]') !== -1);
assert.isTrue(sel.indexOf('[data-thing="hello"]') !== -1);
assert.isTrue(clsIndex < attIndex, 'classes first');
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node2);
});
it('should use only three uncommon but not unique features', function () {
var node1 = document.createElement('div');
node1.setAttribute('data-axe', 'hello');
node1.setAttribute('data-thing', 'hello');
node1.setAttribute('data-thang', 'hello');
node1.className = 'thing thang';
var node2 = document.createElement('div');
node2.setAttribute('data-axe', 'hello');
node2.setAttribute('data-thing', 'hello');
node2.setAttribute('data-thang', 'hello');
node2.className = 'thing thang';
fixtureSetup([node1, node2]);
var sel = axe.utils.getSelector(node2);
var parts = sel.split('.');
parts = parts.reduce(function (val, item) {
var its = item.split('[');
return val.concat(its);
}, []).filter(function (item) {
return item !== '';
});
assert.equal(parts.length, 3);
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node2);
});
it('should use only three uncommon but not unique classes', function () {
var node1 = document.createElement('div');
var node2 = document.createElement('div');
node1.className = 'thing thang thug thick';
node2.className = 'thing thang thug thick';
fixtureSetup([node1, node2]);
var sel = axe.utils.getSelector(node2);
var parts = sel.split('.');
parts = parts.reduce(function (val, item) {
var its = item.split('[');
return val.concat(its);
}, []).filter(function (item) {
return item !== '';
});
assert.equal(parts.length, 3);
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node2);
});
it('should use only three uncommon but not unique attributes', function () {
var node1 = document.createElement('div');
node1.setAttribute('data-axe', 'hello');
node1.setAttribute('data-thug', 'hello');
node1.setAttribute('data-thing', 'hello');
node1.setAttribute('data-thang', 'hello');
var node2 = document.createElement('div');
node2.setAttribute('data-axe', 'hello');
node2.setAttribute('data-thing', 'hello');
node2.setAttribute('data-thang', 'hello');
node2.setAttribute('data-thug', 'hello');
fixtureSetup([node1, node2]);
var sel = axe.utils.getSelector(node2);
var parts = sel.split('.');
parts = parts.reduce(function (val, item) {
var its = item.split('[');
return val.concat(its);
}, []).filter(function (item) {
return item !== '';
});
assert.equal(parts.length, 4);
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node2);
});
it('should not use long attributes', function () {
var node = makeNonuniqueLongAttributes(fixture);
fixtureSetup();
var sel = axe.utils.getSelector(node, {});
assert.isTrue(sel.indexOf('data-att') === -1);
});
it('should use :root when not unique html element', function () {
var node = document.createElement('html');
node.setAttribute('lang', 'en');
fixtureSetup(node);
var sel = axe.utils.getSelector(document.documentElement, {});
assert.equal(sel, ':root');
});
it('should use position if classes are not unique', function () {
var node1 = document.createElement('div');
node1.className = 'monkeys simian';
var node2 = document.createElement('div');
node2.className = 'monkeys simian';
fixtureSetup([node1, node2]);
var sel = axe.utils.getSelector(node2);
assert.equal(sel, '.monkeys.simian:nth-child(2)');
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node2);
});
it('should work on the documentElement', function () {
fixtureSetup();
var sel = axe.utils.getSelector(document.documentElement);
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], document.documentElement);
});
it('should work on the documentElement with classes', function () {
var orig = document.documentElement.className;
document.documentElement.className = 'stuff and other things';
fixtureSetup();
var sel = axe.utils.getSelector(document.documentElement);
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], document.documentElement);
document.documentElement.className = orig;
});
it('should work on the body', function () {
fixtureSetup();
var sel = axe.utils.getSelector(document.body);
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], document.body);
});
it('should work on namespaced elements', function () {
fixtureSetup('<hx:include>Hello</hx:include>');
var node = fixture.firstChild;
var sel = axe.utils.getSelector(node);
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node);
});
it('should work on complex namespaced elements', function () {
fixtureSetup('<m:math xmlns:m="http://www.w3.org/1998/Math/MathML">' +
'<m:mi>x</m:mi>' +
'<m:annotation-xml encoding="MathML-Content">' +
'<m:ci>x</m:ci>' +
'</m:annotation-xml>' +
'</m:math>');
var node = fixture.querySelector('m\\:ci');
var sel = axe.utils.getSelector(node);
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node);
});
it('should not use ignored attributes', function () {
var node = document.createElement('div');
var ignoredAttributes = [
'style',
'selected', 'checked',
'disabled', 'tabindex',
'aria-checked', 'aria-selected',
'aria-invalid', 'aria-activedescendant',
'aria-busy', 'aria-disabled', 'aria-expanded',
'aria-grabbed', 'aria-pressed', 'aria-valuenow'
];
ignoredAttributes.forEach(function (att) {
node.setAttribute(att, 'true');
});
fixtureSetup(node);
assert.isTrue(
axe.utils.getSelector(node).indexOf('[') === -1
);
});
it('should use href and src attributes, shortened', function () {
var link1 = document.createElement('a');
link1.setAttribute('href', '//deque.com/thang/');
var link2 = document.createElement('a');
link2.setAttribute('href', '//deque.com/about/');
var img1 = document.createElement('img');
img1.setAttribute('src', '//deque.com/thang.png');
var img2 = document.createElement('img');
img2.setAttribute('src', '//deque.com/logo.png');
fixtureSetup([ link1, link2, img1, img2 ]);
assert.equal(
axe.utils.getSelector(link2),
'a[href$="about/"]'
);
assert.equal(
axe.utils.getSelector(img2),
'img[src$="logo.png"]'
);
});
it('should not generate universal selectors', function () {
var node = document.createElement('div');
node.setAttribute('role', 'menuitem');
fixtureSetup(node);
assert.equal(
axe.utils.getSelector(node),
'div[role="menuitem"]'
);
});
it('should work correctly when a URL attribute cannot be shortened', function () {
var href1 = 'mars2.html?a=be_bold';
var node1 = document.createElement('a');
node1.setAttribute('href', href1);
var href2 = 'mars2.html?a=be_italic';
var node2 = document.createElement('a');
node2.setAttribute('href', href2);
fixtureSetup([node1, node2]);
assert.include(axe.utils.getSelector(node1), href1);
assert.include(axe.utils.getSelector(node2), href2);
});
// shadow DOM v1 - note: v0 is compatible with this code, so no need
// to specifically test this
(shadowSupported ? it : xit)
('no options: should work with shadow DOM', function () {
var shadEl;
fixture.innerHTML = '<div></div>';
makeShadowTreeGetSelector(fixture.firstChild);
fixtureSetup();
shadEl = fixture.firstChild.shadowRoot.querySelector('input#myinput');
assert.deepEqual(axe.utils.getSelector(shadEl), [
'#fixture > div',
'#myinput'
]);
});
// shadow DOM v1 - note: v0 is compatible with this code, so no need
// to specifically test this
(shadowSupported ? it : xit)
('toRoot: should work with shadow DOM', function () {
var shadEl;
fixture.innerHTML = '<div></div>';
makeShadowTreeGetSelector(fixture.firstChild);
axe._tree = axe.utils.getFlattenedTree(document);
axe._selectorData = axe.utils.getSelectorData(axe._tree);
shadEl = fixture.firstChild.shadowRoot.querySelector('input#myinput');
assert.deepEqual(axe.utils.getSelector(shadEl, { toRoot: true }), [
'html > body > #fixture > div',
'.parent > div > #myinput'
]);
});
it('should correctly calculate unique selector when no discernable features', function () {
var node = makeNonunique(fixture);
fixtureSetup();
var sel = axe.utils.getSelector(node, {});
var mine = document.querySelector(sel);
assert.isTrue(mine === node);
});
it('should not traverse further up than required when no discernable features', function () {
var node = makeNonunique(fixture);
fixtureSetup();
var top = fixture.querySelector('div:nth-child(4)');
var sel = axe.utils.getSelector(node, {});
sel = sel.substring(0, sel.indexOf(' >'));
var test = document.querySelector(sel);
assert.isTrue(test === top);
});
});