diff --git a/axe.d.ts b/axe.d.ts index eb285d9c2c..febd1584cb 100644 --- a/axe.d.ts +++ b/axe.d.ts @@ -41,7 +41,7 @@ declare namespace axe { values: TagValue[] | string[]; } interface RunOptions { - runOnly?: RunOnly; + runOnly?: RunOnly | TagValue[] | string[]; rules?: Object; iframes?: boolean; elementRef?: boolean; diff --git a/doc/API.md b/doc/API.md index dd0e60ccf7..117477e28d 100644 --- a/doc/API.md +++ b/doc/API.md @@ -475,6 +475,16 @@ axe.run( This example will only run the rules with the id of `ruleId1`, `ruleId2`, and `ruleId3`. No other rule will run. +Alternatively, runOnly can be passed an array of rules: + +```js +axe.run({ + runOnly: ['ruleId1', 'ruleId2', 'ruleId3']; +}, (err, results) => { + // ... +}) +``` + 3. Run all enabled Rules except for a list of rules The default operation for axe.run is to run all rules except for rules with the "experimental" tag. If certain rules should be disabled from being run, specify `options` as: diff --git a/lib/core/base/audit.js b/lib/core/base/audit.js index 9a27056c20..497295e718 100644 --- a/lib/core/base/audit.js +++ b/lib/core/base/audit.js @@ -629,13 +629,37 @@ Audit.prototype.normalizeOptions = function(options) { 'use strict'; var audit = this; + const tags = []; + const ruleIds = []; + audit.rules.forEach(rule => { + ruleIds.push(rule.id); + rule.tags.forEach(tag => { + if (!tags.includes(tag)) { + tags.push(tag); + } + }); + }); + // Validate runOnly if (typeof options.runOnly === 'object') { if (Array.isArray(options.runOnly)) { - options.runOnly = { - type: 'tag', - values: options.runOnly - }; + const hasTag = options.runOnly.find(value => tags.includes(value)); + const hasRule = options.runOnly.find(value => ruleIds.includes(value)); + + if (hasTag && hasRule) { + throw new Error('runOnly cannot be both rules and tags'); + } + if (hasRule) { + options.runOnly = { + type: 'rule', + values: options.runOnly + }; + } else { + options.runOnly = { + type: 'tag', + values: options.runOnly + }; + } } const only = options.runOnly; if (only.value && !only.values) { @@ -651,7 +675,7 @@ Audit.prototype.normalizeOptions = function(options) { if (['rule', 'rules'].includes(only.type)) { only.type = 'rule'; only.values.forEach(function(ruleId) { - if (!audit.getRule(ruleId)) { + if (!ruleIds.includes(ruleId)) { throw new Error('unknown rule `' + ruleId + '` in options.runOnly'); } }); @@ -659,11 +683,7 @@ Audit.prototype.normalizeOptions = function(options) { // Validate 'tags' (e.g. anything not 'rule') } 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); + const unmatchedTags = only.values.filter(tag => !tags.includes(tag)); if (unmatchedTags.length !== 0) { axe.log('Could not find tags `' + unmatchedTags.join('`, `') + '`'); @@ -675,7 +695,7 @@ Audit.prototype.normalizeOptions = function(options) { if (typeof options.rules === 'object') { Object.keys(options.rules).forEach(function(ruleId) { - if (!audit.getRule(ruleId)) { + if (!ruleIds.includes(ruleId)) { throw new Error('unknown rule `' + ruleId + '` in options.rules'); } }); diff --git a/test/core/base/audit.js b/test/core/base/audit.js index 4404e7bfaa..10e74d7c11 100644 --- a/test/core/base/audit.js +++ b/test/core/base/audit.js @@ -1265,6 +1265,28 @@ describe('Audit', function() { assert.deepEqual(out.runOnly.values, ['positive', 'negative']); }); + it('allows runOnly as an array as an alternative to type: rule', function() { + var opt = { runOnly: ['positive1', 'negative1'] }; + var out = a.normalizeOptions(opt); + assert(out.runOnly.type, 'rule'); + assert.deepEqual(out.runOnly.values, ['positive1', 'negative1']); + }); + + it('throws an error if runOnly contains both rules and tags', function() { + assert.throws(function() { + a.normalizeOptions({ + runOnly: ['positive', 'negative1'] + }); + }); + }); + + it('defaults runOnly to type: tag', function() { + var opt = { runOnly: ['fakeTag'] }; + var out = a.normalizeOptions(opt); + assert(out.runOnly.type, 'tag'); + assert.deepEqual(out.runOnly.values, ['fakeTag']); + }); + it('throws an error runOnly.values not an array', function() { assert.throws(function() { a.normalizeOptions({ diff --git a/typings/axe-core/axe-core-tests.ts b/typings/axe-core/axe-core-tests.ts index 09866773aa..bc9a10fa89 100644 --- a/typings/axe-core/axe-core-tests.ts +++ b/typings/axe-core/axe-core-tests.ts @@ -69,6 +69,24 @@ axe.run( console.log(error || results); } ); +axe.run( + context, + { + runOnly: ['wcag2a', 'wcag2aa'] + }, + (error: Error, results: axe.AxeResults) => { + console.log(error || results); + } +); +axe.run( + context, + { + runOnly: ['color-contrast', 'heading-order'] + }, + (error: Error, results: axe.AxeResults) => { + console.log(error || results); + } +); var someRulesConfig = { rules: {