Skip to content

Commit 83056ad

Browse files
authored
fix(checks): do not normalize options for custom checks (#2435)
1 parent b315904 commit 83056ad

File tree

6 files changed

+134
-24
lines changed

6 files changed

+134
-24
lines changed

lib/core/base/audit.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import Rule from './rule';
22
import Check from './check';
33
import standards from '../../standards';
4-
import metadataFunctionMap from './metadata-function-map';
54
import RuleResult from './rule-result';
65
import {
76
clone,
@@ -157,7 +156,6 @@ class Audit {
157156
this.lang = 'en';
158157
this.defaultConfig = audit;
159158
this.standards = standards;
160-
this.metadataFunctionMap = metadataFunctionMap;
161159
this._init();
162160
// A copy of the "default" locale. This will be set if the user
163161
// provides a new locale to `axe.configure()` and used to undo

lib/core/base/check.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,21 +180,37 @@ Check.prototype.runSync = function(node, options, context) {
180180
*/
181181

182182
Check.prototype.configure = function(spec) {
183+
// allow test specs (without evaluate functions) to work as
184+
// internal checks
185+
if (!spec.evaluate || metadataFunctionMap[spec.evaluate]) {
186+
this._internalCheck = true;
187+
}
188+
183189
if (spec.hasOwnProperty('enabled')) {
184190
this.enabled = spec.enabled;
185191
}
186192

187193
if (spec.hasOwnProperty('options')) {
188-
this.options = normalizeOptions(spec.options);
194+
// only normalize options for internal checks
195+
if (this._internalCheck) {
196+
this.options = normalizeOptions(spec.options);
197+
} else {
198+
this.options = spec.options;
199+
}
189200
}
190201

191202
['evaluate', 'after']
192203
.filter(prop => spec.hasOwnProperty(prop))
193204
.forEach(prop => (this[prop] = createExecutionContext(spec[prop])));
194205
};
195206

196-
Check.prototype.getOptions = function getOptions(options = {}) {
197-
return deepMerge(this.options, normalizeOptions(options));
207+
Check.prototype.getOptions = function getOptions(options) {
208+
// only merge and normalize options for internal checks
209+
if (this._internalCheck) {
210+
return deepMerge(this.options, normalizeOptions(options || {}));
211+
} else {
212+
return options || this.options;
213+
}
198214
};
199215

200216
export default Check;

lib/core/core.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Audit from './base/audit';
1010
import CheckResult from './base/check-result';
1111
import Check from './base/check';
1212
import Context from './base/context';
13+
import metadataFunctionMap from './base/metadata-function-map';
1314
import RuleResult from './base/rule-result';
1415
import Rule from './base/rule';
1516

@@ -53,7 +54,8 @@ axe._thisWillBeDeletedDoNotUse.base = {
5354
Check,
5455
Context,
5556
RuleResult,
56-
Rule
57+
Rule,
58+
metadataFunctionMap
5759
};
5860

5961
axe.imports = imports;

test/core/base/check.js

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ describe('Check', function() {
33

44
var Check = axe._thisWillBeDeletedDoNotUse.base.Check;
55
var CheckResult = axe._thisWillBeDeletedDoNotUse.base.CheckResult;
6+
var metadataFunctionMap =
7+
axe._thisWillBeDeletedDoNotUse.base.metadataFunctionMap;
68
var noop = function() {};
79

810
var fixture = document.getElementById('fixture');
@@ -99,8 +101,7 @@ describe('Check', function() {
99101
delete Check.prototype.test;
100102
});
101103
it('should override evaluate as ID', function() {
102-
axe._load({});
103-
axe._audit.metadataFunctionMap['custom-evaluate'] = function() {
104+
metadataFunctionMap['custom-evaluate'] = function() {
104105
return 'fong';
105106
};
106107

@@ -113,11 +114,10 @@ describe('Check', function() {
113114
check.configure({ evaluate: 'custom-evaluate' });
114115
assert.equal('fong', check.test());
115116
delete Check.prototype.test;
116-
delete axe._audit.metadataFunctionMap['custom-evaluate'];
117+
delete metadataFunctionMap['custom-evaluate'];
117118
});
118119
it('should override after as ID', function() {
119-
axe._load({});
120-
axe._audit.metadataFunctionMap['custom-after'] = function() {
120+
metadataFunctionMap['custom-after'] = function() {
121121
return 'fong';
122122
};
123123

@@ -130,7 +130,7 @@ describe('Check', function() {
130130
check.configure({ after: 'custom-after' });
131131
assert.equal('fong', check.test());
132132
delete Check.prototype.test;
133-
delete axe._audit.metadataFunctionMap['custom-after'];
133+
delete metadataFunctionMap['custom-after'];
134134
});
135135
it('should error if evaluate does not match an ID', function() {
136136
function fn() {
@@ -226,10 +226,21 @@ describe('Check', function() {
226226
}).run(fixture, { options: expected }, {}, noop);
227227
});
228228

229-
it('should normalize non-object options', function(done) {
229+
it('should normalize non-object options for internal checks', function(done) {
230+
metadataFunctionMap['custom-check'] = function(node, options) {
231+
assert.deepEqual(options, { value: 'foo' });
232+
done();
233+
};
234+
new Check({
235+
evaluate: 'custom-check'
236+
}).run(fixture, { options: 'foo' }, {}, noop);
237+
delete metadataFunctionMap['custom-check'];
238+
});
239+
240+
it('should not normalize non-object options for external checks', function(done) {
230241
new Check({
231242
evaluate: function(node, options) {
232-
assert.deepEqual(options, { value: 'foo' });
243+
assert.deepEqual(options, 'foo');
233244
done();
234245
}
235246
}).run(fixture, { options: 'foo' }, {}, noop);
@@ -388,13 +399,24 @@ describe('Check', function() {
388399
}).runSync(fixture, { options: expected }, {});
389400
});
390401

391-
it('should normalize non-object options', function(done) {
402+
it('should normalize non-object options for internal checks', function(done) {
403+
metadataFunctionMap['custom-check'] = function(node, options) {
404+
assert.deepEqual(options, { value: 'foo' });
405+
done();
406+
};
407+
new Check({
408+
evaluate: 'custom-check'
409+
}).runSync(fixture, { options: 'foo' }, {});
410+
delete metadataFunctionMap['custom-check'];
411+
});
412+
413+
it('should not normalize non-object options for external checks', function(done) {
392414
new Check({
393415
evaluate: function(node, options) {
394-
assert.deepEqual(options, { value: 'foo' });
416+
assert.deepEqual(options, 'foo');
395417
done();
396418
}
397-
}).run(fixture, { options: 'foo' }, {}, noop);
419+
}).runSync(fixture, { options: 'foo' }, {});
398420
});
399421

400422
it('should pass the context through to check evaluate call', function() {
@@ -569,12 +591,20 @@ describe('Check', function() {
569591
assert.equal(new Check(spec).options, spec.options);
570592
});
571593

572-
it('should normalize non-object options', function() {
594+
it('should normalize non-object options for internal checks', function() {
573595
var spec = {
574596
options: 'foo'
575597
};
576598
assert.deepEqual(new Check(spec).options, { value: 'foo' });
577599
});
600+
601+
it('should not normalize non-object options for external checks', function() {
602+
var spec = {
603+
options: 'foo',
604+
evaluate: function() {}
605+
};
606+
assert.deepEqual(new Check(spec).options, 'foo');
607+
});
578608
});
579609

580610
describe('.evaluate', function() {

test/core/base/rule.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ describe('Rule', function() {
33

44
var Rule = axe._thisWillBeDeletedDoNotUse.base.Rule;
55
var Check = axe._thisWillBeDeletedDoNotUse.base.Check;
6+
var metadataFunctionMap =
7+
axe._thisWillBeDeletedDoNotUse.base.metadataFunctionMap;
68
var fixture = document.getElementById('fixture');
79
var noop = function() {};
810
var isNotCalled = function(err) {
@@ -1940,10 +1942,10 @@ describe('Rule', function() {
19401942
});
19411943
it('should override matches (metadata function name)', function() {
19421944
axe._load({});
1943-
axe._audit.metadataFunctionMap['custom-matches'] = function() {
1945+
metadataFunctionMap['custom-matches'] = function() {
19441946
return 'custom-matches';
19451947
};
1946-
axe._audit.metadataFunctionMap['other-matches'] = function() {
1948+
metadataFunctionMap['other-matches'] = function() {
19471949
return 'other-matches';
19481950
};
19491951

@@ -1953,8 +1955,8 @@ describe('Rule', function() {
19531955
rule.configure({ matches: 'other-matches' });
19541956
assert.equal(rule._get('matches')(), 'other-matches');
19551957

1956-
delete axe._audit.metadataFunctionMap['custom-matches'];
1957-
delete axe._audit.metadataFunctionMap['other-matches'];
1958+
delete metadataFunctionMap['custom-matches'];
1959+
delete metadataFunctionMap['other-matches'];
19581960
});
19591961
it('should error if matches does not match an ID', function() {
19601962
function fn() {

test/integration/full/configure-options/configure-options.js

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,68 @@ describe('Configure Options', function() {
3434
}
3535
);
3636
});
37+
38+
it('should not normalize external check options', function(done) {
39+
target.setAttribute('lang', 'en');
40+
41+
axe.configure({
42+
checks: [
43+
{
44+
id: 'dylang',
45+
options: ['dylan'],
46+
evaluate:
47+
'function (node, options) {\n var lang = (node.getAttribute("lang") || "").trim().toLowerCase();\n var xmlLang = (node.getAttribute("xml:lang") || "").trim().toLowerCase();\n var invalid = [];\n (options || []).forEach(function(cc) {\n cc = cc.toLowerCase();\n if (lang && (lang === cc || lang.indexOf(cc.toLowerCase() + "-") === 0)) {\n lang = null;\n }\n if (xmlLang && (xmlLang === cc || xmlLang.indexOf(cc.toLowerCase() + "-") === 0)) {\n xmlLang = null;\n }\n });\n if (xmlLang) {\n invalid.push(\'xml:lang="\' + xmlLang + \'"\');\n }\n if (lang) {\n invalid.push(\'lang="\' + lang + \'"\');\n }\n if (invalid.length) {\n this.data(invalid);\n return true;\n }\n return false;\n }',
48+
messages: {
49+
pass: 'Good language',
50+
fail: 'You mst use the DYLAN language'
51+
}
52+
}
53+
],
54+
rules: [
55+
{
56+
id: 'dylang',
57+
metadata: {
58+
description:
59+
"Ensures lang attributes have the value of 'dylan'",
60+
help: "lang attribute must have the value of 'dylan'"
61+
},
62+
selector: '#target',
63+
any: [],
64+
all: [],
65+
none: ['dylang'],
66+
tags: ['wcag2aa']
67+
}
68+
],
69+
data: {
70+
rules: {
71+
dylang: {
72+
description:
73+
"Ensures lang attributes have the value of 'dylan'",
74+
help: "lang attribute must have the value of 'dylan'"
75+
}
76+
}
77+
}
78+
});
79+
80+
axe.run(
81+
'#target',
82+
{
83+
runOnly: {
84+
type: 'rule',
85+
values: ['dylang']
86+
}
87+
},
88+
function(err, results) {
89+
try {
90+
assert.isNull(err);
91+
assert.lengthOf(results.violations, 1, 'violations');
92+
done();
93+
} catch (e) {
94+
done(e);
95+
}
96+
}
97+
);
98+
});
3799
});
38100

39101
describe('aria-required-attr', function() {
@@ -67,8 +129,8 @@ describe('Configure Options', function() {
67129
});
68130
});
69131

70-
describe('disableOtherRules', function(done) {
71-
it('disables rules that are not in the `rules` array', function() {
132+
describe('disableOtherRules', function() {
133+
it('disables rules that are not in the `rules` array', function(done) {
72134
axe.configure({
73135
disableOtherRules: true,
74136
rules: [

0 commit comments

Comments
 (0)