Skip to content

Commit

Permalink
feat: Make options.runOnly more forgiving about plurality
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Incorrect use of runOnly now throws errors
  • Loading branch information
WilcoFiers committed Mar 12, 2018
1 parent 22607b6 commit fa81f9d
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 33 deletions.
8 changes: 8 additions & 0 deletions doc/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,14 @@ Additionally, there are a number or properties that allow configuration of diffe
}
```
Alternatively, runOnly can be passed an array of tags:
```javascript
{
runOnly: ["wcag2a", "wcag2aa"]
}
```
2. Run only a specified list of Rules
If you only want to run certain rules, specify options as:
Expand Down
57 changes: 34 additions & 23 deletions lib/core/base/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Audit.prototype.addCheck = function (spec) {
*/
Audit.prototype.run = function (context, options, resolve, reject) {
'use strict';
this.validateOptions(options);
this.normalizeOptions(options);

axe._selectCache = [];
var q = axe.utils.queue();
Expand Down Expand Up @@ -220,42 +220,53 @@ Audit.prototype.getRule = function (ruleId) {
* @param {Object} options Options object
* @return {Object} Validated options object
*/
Audit.prototype.validateOptions = function (options) {
Audit.prototype.normalizeOptions = function (options) {
/* eslint max-statements: ["error", 22] */
'use strict';
var audit = this;

// Validate runOnly
if (typeof options.runOnly === 'object') {
var only = options.runOnly;
if (Array.isArray(options.runOnly)) {
options.runOnly = {
type: 'tag',
values: options.runOnly
};
}
const only = options.runOnly;
if (only.value && !only.values) {
only.values = only.value;
delete only.value;
}

if (!Array.isArray(only.values) || only.values.length === 0) {
throw new Error('runOnly.values must be a non-empty array');
}

// Check if every value in options.runOnly is a known rule ID
if (only.type === 'rule' && Array.isArray(only.value)) {
only.value.forEach(function (ruleId) {
if (['rule', 'rules'].includes(only.type)) {
only.type = 'rule';
only.values.forEach(function (ruleId) {
if (!audit.getRule(ruleId)) {
throw new Error('unknown rule `' + ruleId + '` in options.runOnly');
}
});

// Validate 'tags' (e.g. anything not 'rule')
} else if (Array.isArray(only.value) && only.value.length > 0) {
var tags = [].concat(only.value);

audit.rules.forEach(function (rule) {
var tagPos, i, l;
if (!tags) {
return;
}
// Remove any known tag
for (i = 0, l = rule.tags.length; i < l; i++) {
tagPos = tags.indexOf(rule.tags[i]);
if (tagPos !== -1) {
tags.splice(tagPos, 1);
}
}
});
if (tags.length !== 0) {
throw new Error('could not find tags `' + tags.join('`, `') + '`');
} else if (['tag', 'tags', undefined].includes(only.type)) {
only.type = 'tag';
const unmatchedTags = audit.rules.reduce((unmatchedTags, rule) => {
return (unmatchedTags.length
? unmatchedTags.filter(tag => !rule.tags.includes(tag))
: unmatchedTags
);
}, only.values);

if (unmatchedTags.length !== 0) {
throw new Error('Could not find tags `' + unmatchedTags.join('`, `') + '`');
}
} else {
throw new Error(`Unknown runOnly type '${only.type}'`);
}
}

Expand Down
105 changes: 95 additions & 10 deletions test/core/base/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,24 @@ describe('Audit', function () {
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']
}];

Expand Down Expand Up @@ -621,14 +625,14 @@ describe('Audit', function () {
}, isNotCalled);
});

it('should run audit.validateOptions to ensure valid input', function () {
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.validateOptions = function () {
a.normalizeOptions = function () {
checked = 'options validated';
};

Expand Down Expand Up @@ -687,46 +691,127 @@ describe('Audit', function () {
});
});

describe('Audit#validateOptions', function () {
describe('Audit#normalizeOptions', function () {

it('returns the options object when it is valid', function () {
var opt = {
runOnly: {
type: 'rule',
value: ['positive1', 'positive2']
values: ['positive1', 'positive2']
},
rules: {
negative1: { enabled: false }
}
};
assert(a.validateOptions(opt), opt);
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.validateOptions({
a.normalizeOptions({
runOnly: {
type: 'rule',
value: ['frakeRule']
values: ['frakeRule']
}
});
});
});

it('throws an error when option.runOnly has an unknown tag', function () {
assert.throws(function () {
a.validateOptions({
a.normalizeOptions({
runOnly: {
type: 'tags',
value: ['fakeTag']
values: ['fakeTag']
}
});
});
});

it('throws an error when option.rules has an unknown rule', function () {
assert.throws(function () {
a.validateOptions({
a.normalizeOptions({
rules: {
fakeRule: { enabled: false}
}
Expand Down

0 comments on commit fa81f9d

Please sign in to comment.