axe-core
Version:
Accessibility engine for automated Web UI testing
825 lines (756 loc) • 21.2 kB
JavaScript
/*global Audit, Rule */
describe('Audit', function () {
'use strict';
var ver = axe.version.substring(0, axe.version.lastIndexOf('.'));
var a, getFlattenedTree;
var isNotCalled = function (err) {
throw err || new Error('Reject should not be called');
};
var noop = function () {};
var mockChecks = [{
id: 'positive1-check1',
evaluate: function () {
return true;
}
}, {
id: 'positive2-check1',
evaluate: function () {
return true;
}
}, {
id: 'negative1-check1',
evaluate: function () {
return true;
}
}, {
id: 'positive3-check1',
evaluate: function () {
return true;
}
}];
var mockRules = [{
id: 'positive1',
selector: 'input',
tags: ['positive'],
any: [{
id: 'positive1-check1',
}]
}, {
id: 'positive2',
selector: '#monkeys',
tags: ['positive'],
any: ['positive2-check1']
}, {
id: 'negative1',
selector: 'div',
tags: ['negative'],
none: ['negative1-check1']
}, {
id: 'positive3',
selector: 'blink',
tags: ['positive'],
any: ['positive3-check1']
}];
var fixture = document.getElementById('fixture');
beforeEach(function () {
a = new Audit();
mockRules.forEach(function (r) {
a.addRule(r);
});
mockChecks.forEach(function (c) {
a.addCheck(c);
});
getFlattenedTree = axe.utils.getFlattenedTree;
});
afterEach(function () {
fixture.innerHTML = '';
axe._tree = undefined;
axe._selectCache = undefined;
axe.utils.getFlattenedTree = getFlattenedTree;
});
it('should be a function', function () {
assert.isFunction(Audit);
});
describe('defaults', function() {
it('should set noHtml', function() {
var audit = new Audit();
assert.isFalse(audit.noHtml);
});
});
describe('Audit#_constructHelpUrls', function () {
it('should create default help URLS', function () {
var audit = new Audit();
audit.addRule({
id: 'target',
matches: 'function () {return "hello";}',
selector: 'bob'
});
assert.lengthOf(audit.rules, 1);
assert.equal(audit.data.rules.target, undefined);
audit._constructHelpUrls();
assert.deepEqual(audit.data.rules.target, {
helpUrl: 'https://dequeuniversity.com/rules/axe/' + ver + '/target?application=axeAPI'
});
});
it('should use changed branding', function () {
var audit = new Audit();
audit.addRule({
id: 'target',
matches: 'function () {return "hello";}',
selector: 'bob'
});
assert.lengthOf(audit.rules, 1);
assert.equal(audit.data.rules.target, undefined);
audit.brand = 'thing';
audit._constructHelpUrls();
assert.deepEqual(audit.data.rules.target, {
helpUrl: 'https://dequeuniversity.com/rules/thing/' + ver + '/target?application=axeAPI'
});
});
it('should use changed application', function () {
var audit = new Audit();
audit.addRule({
id: 'target',
matches: 'function () {return "hello";}',
selector: 'bob'
});
assert.lengthOf(audit.rules, 1);
assert.equal(audit.data.rules.target, undefined);
audit.application = 'thing';
audit._constructHelpUrls();
assert.deepEqual(audit.data.rules.target, {
helpUrl: 'https://dequeuniversity.com/rules/axe/' + ver + '/target?application=thing'
});
});
it('does not override helpUrls of different products', function () {
var audit = new Audit();
audit.addRule({
id: 'target1',
matches: 'function () {return "hello";}',
selector: 'bob',
metadata: {
helpUrl: 'https://dequeuniversity.com/rules/myproject/' + ver + '/target1?application=axeAPI'
}
});
audit.addRule({
id: 'target2',
matches: 'function () {return "hello";}',
selector: 'bob'
});
assert.equal(
audit.data.rules.target1.helpUrl,
'https://dequeuniversity.com/rules/myproject/' + ver + '/target1?application=axeAPI'
);
assert.isUndefined(audit.data.rules.target2);
assert.lengthOf(audit.rules, 2);
audit.brand = 'thing';
audit._constructHelpUrls();
assert.equal(
audit.data.rules.target1.helpUrl,
'https://dequeuniversity.com/rules/myproject/' + ver + '/target1?application=axeAPI'
);
assert.equal(
audit.data.rules.target2.helpUrl,
'https://dequeuniversity.com/rules/thing/' + ver + '/target2?application=axeAPI'
);
});
it('understands prerelease type version numbers', function () {
var tempVersion = axe.version;
var audit = new Audit();
audit.addRule({
id: 'target',
matches: 'function () {return "hello";}',
selector: 'bob'
});
axe.version = '3.2.1-alpha.0';
audit._constructHelpUrls();
axe.version = tempVersion;
assert.equal(audit.data.rules.target.helpUrl,
'https://dequeuniversity.com/rules/axe/3.2/target?application=axeAPI');
});
it('matches major release versions', function () {
var tempVersion = axe.version;
var audit = new Audit();
audit.addRule({
id: 'target',
matches: 'function () {return "hello";}',
selector: 'bob'
});
axe.version = '1.0.0';
audit._constructHelpUrls();
axe.version = tempVersion;
assert.equal(audit.data.rules.target.helpUrl,
'https://dequeuniversity.com/rules/axe/1.0/target?application=axeAPI');
});
});
describe('Audit#setBranding', function () {
it('should change the brand', function () {
var audit = new Audit();
assert.equal(audit.brand, 'axe');
assert.equal(audit.application, 'axeAPI');
audit.setBranding({
brand: 'thing'
});
assert.equal(audit.brand, 'thing');
assert.equal(audit.application, 'axeAPI');
});
it('should change the application', function () {
var audit = new Audit();
assert.equal(audit.brand, 'axe');
assert.equal(audit.application, 'axeAPI');
audit.setBranding({
application: 'thing'
});
assert.equal(audit.brand, 'axe');
assert.equal(audit.application, 'thing');
});
it('should call _constructHelpUrls', function () {
var audit = new Audit();
audit.addRule({
id: 'target',
matches: 'function () {return "hello";}',
selector: 'bob'
});
assert.lengthOf(audit.rules, 1);
assert.equal(audit.data.rules.target, undefined);
audit.setBranding({
application: 'thing'
});
assert.deepEqual(audit.data.rules.target, {
helpUrl: 'https://dequeuniversity.com/rules/axe/' + ver + '/target?application=thing'
});
});
it('should call _constructHelpUrls even when nothing changed', function () {
var audit = new Audit();
audit.addRule({
id: 'target',
matches: 'function () {return "hello";}',
selector: 'bob'
});
assert.lengthOf(audit.rules, 1);
assert.equal(audit.data.rules.target, undefined);
audit.setBranding(undefined);
assert.deepEqual(audit.data.rules.target, {
helpUrl: 'https://dequeuniversity.com/rules/axe/' + ver + '/target?application=axeAPI'
});
});
it('should not replace custom set branding', function () {
var audit = new Audit();
audit.addRule({
id: 'target',
matches: 'function () {return "hello";}',
selector: 'bob',
metadata: {
helpUrl: 'https://dequeuniversity.com/rules/customer-x/' + ver + '/target?application=axeAPI'
}
});
audit.setBranding({
application: 'thing',
brand: 'other'
});
assert.equal(
audit.data.rules.target.helpUrl,
'https://dequeuniversity.com/rules/customer-x/' + ver + '/target?application=axeAPI'
);
});
});
describe('Audit#addRule', function () {
it('should override existing rule', function () {
var audit = new Audit();
audit.addRule({
id: 'target',
matches: 'function () {return "hello";}',
selector: 'bob'
});
assert.lengthOf(audit.rules, 1);
assert.equal(audit.rules[0].selector, 'bob');
assert.equal(audit.rules[0].matches(), 'hello');
audit.addRule({
id: 'target',
selector: 'fred'
});
assert.lengthOf(audit.rules, 1);
assert.equal(audit.rules[0].selector, 'fred');
assert.equal(audit.rules[0].matches(), 'hello');
});
it('should otherwise push new rule', function () {
var audit = new Audit();
audit.addRule({
id: 'target',
selector: 'bob'
});
assert.lengthOf(audit.rules, 1);
assert.equal(audit.rules[0].id, 'target');
assert.equal(audit.rules[0].selector, 'bob');
audit.addRule({
id: 'target2',
selector: 'fred'
});
assert.lengthOf(audit.rules, 2);
assert.equal(audit.rules[1].id, 'target2');
assert.equal(audit.rules[1].selector, 'fred');
});
});
describe('Audit#resetRulesAndChecks', function () {
it('should override newly created check', function () {
var audit = new Audit();
assert.equal(audit.checks.target, undefined);
audit.addCheck({
id: 'target',
options: 'jane'
});
assert.ok(audit.checks.target);
assert.equal(audit.checks.target.options, 'jane');
audit.resetRulesAndChecks();
assert.equal(audit.checks.target, undefined);
});
it('should reset noHtml', function() {
var audit = new Audit();
audit.noHtml = true;
audit.resetRulesAndChecks();
assert.isFalse(audit.noHtml);
});
});
describe('Audit#addCheck', function () {
it('should create a new check', function () {
var audit = new Audit();
assert.equal(audit.checks.target, undefined);
audit.addCheck({
id: 'target',
options: 'jane'
});
assert.ok(audit.checks.target);
assert.equal(audit.checks.target.options, 'jane');
});
it('should configure the metadata, if passed', function () {
var audit = new Audit();
assert.equal(audit.checks.target, undefined);
audit.addCheck({
id: 'target',
metadata: {guy:'bob'}
});
assert.ok(audit.checks.target);
assert.equal(audit.data.checks.target.guy, 'bob');
});
it('should reconfigure existing check', function () {
var audit = new Audit();
var myTest = function () {};
audit.addCheck({
id: 'target',
evaluate: myTest,
options: 'jane',
});
assert.equal(audit.checks.target.options, 'jane');
audit.addCheck({
id: 'target',
options: 'fred'
});
assert.equal(audit.checks.target.evaluate, myTest);
assert.equal(audit.checks.target.options, 'fred');
});
it('should not turn messages into a function', function () {
var audit = new Audit();
var spec = {
id: 'target',
evaluate: 'function () { return "blah";}',
metadata: {
messages: {
fail: 'it failed'
}
}
};
audit.addCheck(spec);
assert.equal(typeof audit.checks.target.evaluate, 'function');
assert.equal(typeof audit.data.checks.target.messages.fail, 'string');
assert.equal(audit.data.checks.target.messages.fail, 'it failed');
});
it('should turn function strings into a function', function () {
var audit = new Audit();
var spec = {
id: 'target',
evaluate: 'function () { return "blah";}',
metadata: {
messages: {
fail: 'function () {return "it failed";}'
}
}
};
audit.addCheck(spec);
assert.equal(typeof audit.checks.target.evaluate, 'function');
assert.equal(typeof audit.data.checks.target.messages.fail, 'function');
assert.equal(audit.data.checks.target.messages.fail(), 'it failed');
});
});
describe('Audit#run', function () {
it('should run all the rules', function (done) {
fixture.innerHTML = '<input aria-label="monkeys" type="text">' +
'<div id="monkeys">bananas</div>' +
'<input aria-labelledby="monkeys">' +
'<blink>FAIL ME</blink>';
a.run({ include: [axe.utils.getFlattenedTree(fixture)[0]] }, {}, function (results) {
var expected = [{
id: 'positive1',
result: 'inapplicable',
pageLevel: false,
impact: null,
nodes: '...other tests cover this...'
}, {
id: 'positive2',
result: 'inapplicable',
pageLevel: false,
impact: null,
nodes: '...other tests cover this...'
}, {
id: 'negative1',
result: 'inapplicable',
pageLevel: false,
impact: null,
nodes: '...other tests cover this...'
}, {
id: 'positive3',
result: 'inapplicable',
pageLevel: false,
impact: null,
nodes: '...other tests cover this...'
}];
var out = results[0].nodes[0].node.source;
results.forEach(function (res) {
// attribute order is a pain in the lower back in IE, so we're not
// comparing nodes. Check.run and Rule.run do this.
res.nodes = '...other tests cover this...';
});
assert.deepEqual(JSON.parse(JSON.stringify(results)), expected);
assert.match(out, /^<input(\s+type="text"|\s+aria-label="monkeys"){2,}>/);
done();
}, isNotCalled);
});
it('should not run rules disabled by the options', function (done) {
a.run({ include: [document] }, {
rules: {
'positive3': {
enabled: false
}
}
}, function (results) {
assert.equal(results.length, 3);
done();
}, isNotCalled);
});
it('should assign an empty array to axe._selectCache', function (done) {
var saved = axe.utils.ruleShouldRun;
axe.utils.ruleShouldRun = function () {
assert.equal(axe._selectCache.length, 0);
return false;
};
a.run({ include: [document] }, {}, function () {
axe.utils.ruleShouldRun = saved;
done();
}, isNotCalled);
});
it('should clear axe._selectCache', function (done) {
a.run({ include: [document] }, {
rules: {}
}, function () {
assert.isTrue(typeof axe._selectCache === 'undefined');
done();
}, isNotCalled);
});
it('should not run rules disabled by the configuration', function (done) {
var a = new Audit();
var success = true;
a.rules.push(new Rule({
id: 'positive1',
selector: '*',
enabled: false,
any: [{
id: 'positive1-check1',
evaluate: function () {
success = false;
}
}]
}));
a.run({ include: [document] }, {}, function () {
assert.ok(success);
done();
}, isNotCalled);
});
it('should call the rule\'s run function', function (done) {
var targetRule = mockRules[mockRules.length - 1],
rule = axe.utils.findBy(a.rules, 'id', targetRule.id),
called = false,
orig;
fixture.innerHTML = '<a href="#">link</a>';
orig = rule.run;
rule.run = function (node, options, callback) {
called = true;
callback({});
};
a.run({ include: [document] }, {}, function () {
assert.isTrue(called);
rule.run = orig;
done();
}, isNotCalled);
});
it('should pass the option to the run function', function (done) {
var targetRule = mockRules[mockRules.length - 1],
rule = axe.utils.findBy(a.rules, 'id', targetRule.id),
passed = false,
orig, options;
fixture.innerHTML = '<a href="#">link</a>';
orig = rule.run;
rule.run = function (node, o, callback) {
assert.deepEqual(o, options);
passed = true;
callback({});
};
options = {rules: {}};
(options.rules[targetRule.id] = {}).data = 'monkeys';
a.run({ include: [document] }, options, function () {
assert.ok(passed);
rule.run = orig;
done();
}, isNotCalled);
});
it('should skip pageLevel rules if context is not set to entire page', function () {
var audit = new Audit();
audit.rules.push(new Rule({
pageLevel: true,
enabled: true,
evaluate: function () {
assert.ok(false, 'Should not run');
}
}));
audit.run({ include: [ document.body ], page: false }, {}, function (results) {
assert.deepEqual(results, []);
}, isNotCalled);
});
it('catches errors and passes them as a cantTell result', function (done) {
var err = new Error('Launch the super sheep!');
a.addRule({
id: 'throw1',
selector: '*',
any: [{
id: 'throw1-check1',
}]
});
a.addCheck({
id: 'throw1-check1',
evaluate: function () {
throw err;
}
});
a.run({ include: [axe.utils.getFlattenedTree(fixture)[0]] }, {
runOnly: {
'type': 'rule',
'values': ['throw1']
}
}, function (results) {
assert.lengthOf(results,1);
assert.equal(results[0].result, 'cantTell');
assert.equal(results[0].message, err.message);
assert.equal(results[0].stack, err.stack);
assert.equal(results[0].error, err);
done();
}, isNotCalled);
});
it('should not halt if errors occur', function (done) {
a.addRule({
id: 'throw1',
selector: '*',
any: [{
id: 'throw1-check1',
}]
});
a.addCheck({
id: 'throw1-check1',
evaluate: function () {
throw new Error('Launch the super sheep!');
}
});
a.run({ include: [axe.utils.getFlattenedTree(fixture)[0]] }, {
runOnly: {
'type': 'rule',
'values': ['throw1', 'positive1']
}
}, function () {
done();
}, isNotCalled);
});
it('should run audit.normalizeOptions to ensure valid input', function () {
fixture.innerHTML = '<input type="text" aria-label="monkeys">' +
'<div id="monkeys">bananas</div>' +
'<input aria-labelledby="monkeys" type="text">' +
'<blink>FAIL ME</blink>';
var checked = 'options not validated';
a.normalizeOptions = function () {
checked = 'options validated';
};
a.run({ include: [fixture] }, {}, noop, isNotCalled);
assert.equal(checked, 'options validated');
});
it('should halt if an error occurs when debug is set', function (done) {
a.addRule({
id: 'throw1',
selector: '*',
any: [{
id: 'throw1-check1',
}]
});
a.addCheck({
id: 'throw1-check1',
evaluate: function () {
throw new Error('Launch the super sheep!');
}
});
a.run({ include: [axe.utils.getFlattenedTree(fixture)[0]] }, {
debug: true,
runOnly: {
'type': 'rule',
'values': ['throw1']
}
}, noop, function (err) {
assert.equal(err.message, 'Launch the super sheep!');
done();
});
});
});
describe('Audit#after', function () {
it('should run Rule#after on any rule whose result is passed in', function () {
/*eslint no-unused-vars:0*/
var audit = new Audit();
var success = false;
var options = [{ id: 'hehe', enabled: true, monkeys: 'bananas' }];
var results = [{
id: 'hehe',
monkeys: 'bananas'
}];
audit.rules.push(new Rule({
id: 'hehe',
pageLevel: false,
enabled: false
}));
audit.rules[0].after = function (res, opts) {
assert.equal(res, results[0]);
assert.deepEqual(opts, options);
success = true;
};
audit.after(results, options);
});
});
describe('Audit#normalizeOptions', function () {
it('returns the options object when it is valid', function () {
var opt = {
runOnly: {
type: 'rule',
values: ['positive1', 'positive2']
},
rules: {
negative1: { enabled: false }
}
};
assert(a.normalizeOptions(opt), opt);
});
it('allows `value` as alternative to `values`', function () {
var opt = {
runOnly: {
type: 'rule',
value: ['positive1', 'positive2']
}
};
var out = a.normalizeOptions(opt)
assert.deepEqual(out.runOnly.values, ['positive1', 'positive2']);
assert.isUndefined(out.runOnly.value);
});
it('allows type: rules as an alternative to type: rule', function () {
var opt = {
runOnly: {
type: 'rules',
values: ['positive1', 'positive2']
}
};
assert(a.normalizeOptions(opt).runOnly.type, 'rule');
});
it('allows type: tags as an alternative to type: tag', function () {
var opt = {
runOnly: {
type: 'tags',
values: ['positive']
}
};
assert(a.normalizeOptions(opt).runOnly.type, 'tag');
});
it('allows type: undefined as an alternative to type: tag', function () {
var opt = {
runOnly: {
values: ['positive']
}
};
assert(a.normalizeOptions(opt).runOnly.type, 'tag');
});
it('allows runOnly as an array as an alternative to type: tag', function () {
var opt = { runOnly: ['positive', 'negative'] };
var out = a.normalizeOptions(opt);
assert(out.runOnly.type, 'tag');
assert.deepEqual(out.runOnly.values, ['positive', 'negative']);
});
it('throws an error runOnly.values not an array', function () {
assert.throws(function () {
a.normalizeOptions({
runOnly: {
type: 'rule',
values: { badProp: 'badValue' }
}
});
});
});
it('throws an error runOnly.values an empty', function () {
assert.throws(function () {
a.normalizeOptions({
runOnly: {
type: 'rule',
values: []
}
});
});
});
it('throws an error runOnly.type is unknown', function () {
assert.throws(function () {
a.normalizeOptions({
runOnly: {
type: 'something-else',
values: ['wcag2aa']
}
});
});
});
it('throws an error when option.runOnly has an unknown rule', function () {
assert.throws(function () {
a.normalizeOptions({
runOnly: {
type: 'rule',
values: ['frakeRule']
}
});
});
});
it('throws an error when option.runOnly has an unknown tag', function () {
assert.throws(function () {
a.normalizeOptions({
runOnly: {
type: 'tags',
values: ['fakeTag']
}
});
});
});
it('throws an error when option.rules has an unknown rule', function () {
assert.throws(function () {
a.normalizeOptions({
rules: {
fakeRule: { enabled: false}
}
});
});
});
});
});