Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(audit): allow runOnly option to accept an array of rules #1889

Merged
merged 3 commits into from
Nov 12, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions doc/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ The current set of tags supported are listed in the following table:

| Tag Name | Accessibility Standard/Purpose |
| --------------- | :-----------------------------------------: |
| `wcag2a` | WCAG 2.0 & WCAG 2.1 Level A |
| `wcag2aa` | WCAG 2.0 & WCAG 2.1 Level AA |
| `wcag2a` | WCAG 2.0 & WCAG 2.1 Level A |
| `wcag2aa` | WCAG 2.0 & WCAG 2.1 Level AA |
| `wcag21a` | WCAG 2.1 Level A |
| `wcag21aa` | WCAG 2.1 Level AA |
| `section508` | Section 508 |
Expand Down Expand Up @@ -397,7 +397,7 @@ Additionally, there are a number or properties that allow configuration of diffe
| `elementRef` | `false` | Return element references in addition to the target |
| `restoreScroll` | `false` | Scrolls elements back to before axe started |
| `frameWaitTime` | `60000` | How long (in milliseconds) axe waits for a response from embedded frames before timing out |
| `preload` | `true` | Any additional assets (eg: cssom) to preload before running rules. [See here for configuration details](#preload-configuration-details) |
| `preload` | `true` | Any additional assets (eg: cssom) to preload before running rules. [See here for configuration details](#preload-configuration-details) |
| `performanceTimer` | `false` | Log rule performance metrics to the console |

###### Options Parameter Examples
Expand Down Expand Up @@ -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'];
straker marked this conversation as resolved.
Show resolved Hide resolved
}, (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:
Expand Down
43 changes: 32 additions & 11 deletions lib/core/base/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -651,19 +675,16 @@ 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');
}
});

// 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);
only.type = 'tag';
straker marked this conversation as resolved.
Show resolved Hide resolved
const unmatchedTags = only.values.filter(tag => !tags.includes(tag));

if (unmatchedTags.length !== 0) {
axe.log('Could not find tags `' + unmatchedTags.join('`, `') + '`');
Expand All @@ -675,7 +696,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');
}
});
Expand Down
22 changes: 22 additions & 0 deletions test/core/base/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down