Skip to content

Commit fa81f9d

Browse files
committed
feat: Make options.runOnly more forgiving about plurality
BREAKING CHANGE: Incorrect use of runOnly now throws errors
1 parent 22607b6 commit fa81f9d

File tree

3 files changed

+137
-33
lines changed

3 files changed

+137
-33
lines changed

doc/API.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,14 @@ Additionally, there are a number or properties that allow configuration of diffe
376376
}
377377
```
378378

379+
Alternatively, runOnly can be passed an array of tags:
380+
381+
```javascript
382+
{
383+
runOnly: ["wcag2a", "wcag2aa"]
384+
}
385+
```
386+
379387
2. Run only a specified list of Rules
380388

381389
If you only want to run certain rules, specify options as:

lib/core/base/audit.js

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ Audit.prototype.addCheck = function (spec) {
142142
*/
143143
Audit.prototype.run = function (context, options, resolve, reject) {
144144
'use strict';
145-
this.validateOptions(options);
145+
this.normalizeOptions(options);
146146

147147
axe._selectCache = [];
148148
var q = axe.utils.queue();
@@ -220,42 +220,53 @@ Audit.prototype.getRule = function (ruleId) {
220220
* @param {Object} options Options object
221221
* @return {Object} Validated options object
222222
*/
223-
Audit.prototype.validateOptions = function (options) {
223+
Audit.prototype.normalizeOptions = function (options) {
224+
/* eslint max-statements: ["error", 22] */
224225
'use strict';
225226
var audit = this;
226227

227228
// Validate runOnly
228229
if (typeof options.runOnly === 'object') {
229-
var only = options.runOnly;
230+
if (Array.isArray(options.runOnly)) {
231+
options.runOnly = {
232+
type: 'tag',
233+
values: options.runOnly
234+
};
235+
}
236+
const only = options.runOnly;
237+
if (only.value && !only.values) {
238+
only.values = only.value;
239+
delete only.value;
240+
}
241+
242+
if (!Array.isArray(only.values) || only.values.length === 0) {
243+
throw new Error('runOnly.values must be a non-empty array');
244+
}
230245

231246
// Check if every value in options.runOnly is a known rule ID
232-
if (only.type === 'rule' && Array.isArray(only.value)) {
233-
only.value.forEach(function (ruleId) {
247+
if (['rule', 'rules'].includes(only.type)) {
248+
only.type = 'rule';
249+
only.values.forEach(function (ruleId) {
234250
if (!audit.getRule(ruleId)) {
235251
throw new Error('unknown rule `' + ruleId + '` in options.runOnly');
236252
}
237253
});
238254

239255
// Validate 'tags' (e.g. anything not 'rule')
240-
} else if (Array.isArray(only.value) && only.value.length > 0) {
241-
var tags = [].concat(only.value);
242-
243-
audit.rules.forEach(function (rule) {
244-
var tagPos, i, l;
245-
if (!tags) {
246-
return;
247-
}
248-
// Remove any known tag
249-
for (i = 0, l = rule.tags.length; i < l; i++) {
250-
tagPos = tags.indexOf(rule.tags[i]);
251-
if (tagPos !== -1) {
252-
tags.splice(tagPos, 1);
253-
}
254-
}
255-
});
256-
if (tags.length !== 0) {
257-
throw new Error('could not find tags `' + tags.join('`, `') + '`');
256+
} else if (['tag', 'tags', undefined].includes(only.type)) {
257+
only.type = 'tag';
258+
const unmatchedTags = audit.rules.reduce((unmatchedTags, rule) => {
259+
return (unmatchedTags.length
260+
? unmatchedTags.filter(tag => !rule.tags.includes(tag))
261+
: unmatchedTags
262+
);
263+
}, only.values);
264+
265+
if (unmatchedTags.length !== 0) {
266+
throw new Error('Could not find tags `' + unmatchedTags.join('`, `') + '`');
258267
}
268+
} else {
269+
throw new Error(`Unknown runOnly type '${only.type}'`);
259270
}
260271
}
261272

test/core/base/audit.js

Lines changed: 95 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,24 @@ describe('Audit', function () {
3232
var mockRules = [{
3333
id: 'positive1',
3434
selector: 'input',
35+
tags: ['positive'],
3536
any: [{
3637
id: 'positive1-check1',
3738
}]
3839
}, {
3940
id: 'positive2',
4041
selector: '#monkeys',
42+
tags: ['positive'],
4143
any: ['positive2-check1']
4244
}, {
4345
id: 'negative1',
4446
selector: 'div',
47+
tags: ['negative'],
4548
none: ['negative1-check1']
4649
}, {
4750
id: 'positive3',
4851
selector: 'blink',
52+
tags: ['positive'],
4953
any: ['positive3-check1']
5054
}];
5155

@@ -621,14 +625,14 @@ describe('Audit', function () {
621625
}, isNotCalled);
622626
});
623627

624-
it('should run audit.validateOptions to ensure valid input', function () {
628+
it('should run audit.normalizeOptions to ensure valid input', function () {
625629
fixture.innerHTML = '<input type="text" aria-label="monkeys">' +
626630
'<div id="monkeys">bananas</div>' +
627631
'<input aria-labelledby="monkeys" type="text">' +
628632
'<blink>FAIL ME</blink>';
629633
var checked = 'options not validated';
630634

631-
a.validateOptions = function () {
635+
a.normalizeOptions = function () {
632636
checked = 'options validated';
633637
};
634638

@@ -687,46 +691,127 @@ describe('Audit', function () {
687691
});
688692
});
689693

690-
describe('Audit#validateOptions', function () {
694+
describe('Audit#normalizeOptions', function () {
691695

692696
it('returns the options object when it is valid', function () {
693697
var opt = {
694698
runOnly: {
695699
type: 'rule',
696-
value: ['positive1', 'positive2']
700+
values: ['positive1', 'positive2']
697701
},
698702
rules: {
699703
negative1: { enabled: false }
700704
}
701705
};
702-
assert(a.validateOptions(opt), opt);
706+
assert(a.normalizeOptions(opt), opt);
707+
});
708+
709+
it('allows `value` as alternative to `values`', function () {
710+
var opt = {
711+
runOnly: {
712+
type: 'rule',
713+
value: ['positive1', 'positive2']
714+
}
715+
};
716+
var out = a.normalizeOptions(opt)
717+
assert.deepEqual(out.runOnly.values, ['positive1', 'positive2']);
718+
assert.isUndefined(out.runOnly.value);
719+
});
720+
721+
it('allows type: rules as an alternative to type: rule', function () {
722+
var opt = {
723+
runOnly: {
724+
type: 'rules',
725+
values: ['positive1', 'positive2']
726+
}
727+
};
728+
assert(a.normalizeOptions(opt).runOnly.type, 'rule');
729+
});
730+
731+
it('allows type: tags as an alternative to type: tag', function () {
732+
var opt = {
733+
runOnly: {
734+
type: 'tags',
735+
values: ['positive']
736+
}
737+
};
738+
assert(a.normalizeOptions(opt).runOnly.type, 'tag');
739+
});
740+
741+
it('allows type: undefined as an alternative to type: tag', function () {
742+
var opt = {
743+
runOnly: {
744+
values: ['positive']
745+
}
746+
};
747+
assert(a.normalizeOptions(opt).runOnly.type, 'tag');
748+
});
749+
750+
it('allows runOnly as an array as an alternative to type: tag', function () {
751+
var opt = { runOnly: ['positive', 'negative'] };
752+
var out = a.normalizeOptions(opt);
753+
assert(out.runOnly.type, 'tag');
754+
assert.deepEqual(out.runOnly.values, ['positive', 'negative']);
755+
});
756+
757+
it('throws an error runOnly.values not an array', function () {
758+
assert.throws(function () {
759+
a.normalizeOptions({
760+
runOnly: {
761+
type: 'rule',
762+
values: { badProp: 'badValue' }
763+
}
764+
});
765+
});
766+
});
767+
768+
it('throws an error runOnly.values an empty', function () {
769+
assert.throws(function () {
770+
a.normalizeOptions({
771+
runOnly: {
772+
type: 'rule',
773+
values: []
774+
}
775+
});
776+
});
777+
});
778+
779+
it('throws an error runOnly.type is unknown', function () {
780+
assert.throws(function () {
781+
a.normalizeOptions({
782+
runOnly: {
783+
type: 'something-else',
784+
values: ['wcag2aa']
785+
}
786+
});
787+
});
703788
});
704789

705790
it('throws an error when option.runOnly has an unknown rule', function () {
706791
assert.throws(function () {
707-
a.validateOptions({
792+
a.normalizeOptions({
708793
runOnly: {
709794
type: 'rule',
710-
value: ['frakeRule']
795+
values: ['frakeRule']
711796
}
712797
});
713798
});
714799
});
715800

716801
it('throws an error when option.runOnly has an unknown tag', function () {
717802
assert.throws(function () {
718-
a.validateOptions({
803+
a.normalizeOptions({
719804
runOnly: {
720805
type: 'tags',
721-
value: ['fakeTag']
806+
values: ['fakeTag']
722807
}
723808
});
724809
});
725810
});
726811

727812
it('throws an error when option.rules has an unknown rule', function () {
728813
assert.throws(function () {
729-
a.validateOptions({
814+
a.normalizeOptions({
730815
rules: {
731816
fakeRule: { enabled: false}
732817
}

0 commit comments

Comments
 (0)