diff --git a/axe.d.ts b/axe.d.ts index b68b2c6f46..c373d6ece5 100644 --- a/axe.d.ts +++ b/axe.d.ts @@ -124,6 +124,7 @@ declare namespace axe { checks?: Check[]; rules?: Rule[]; locale?: Locale; + axeVersion?: string; } interface Check { id: string; diff --git a/doc/API.md b/doc/API.md index 109a51eb73..48b0c2c7ea 100644 --- a/doc/API.md +++ b/doc/API.md @@ -164,7 +164,8 @@ axe.configure({ reporter: 'option', checks: [Object], rules: [Object], - locale: Object + locale: Object, + axeVersion: String }); ``` @@ -203,6 +204,7 @@ axe.configure({ - `matches` - string(optional, default `*`). A filtering [CSS selector](./developer-guide.md#supported-css-selectors) that will exclude elements that do not match the CSS selector. - `disableOtherRules` - Disables all rules not included in the `rules` property. - `locale` - A locale object to apply (at runtime) to all rules and checks, in the same shape as `/locales/*.json`. + - `axeVersion` - Set the compatible version of a custom rule with the current axe version. Compatible versions are all patch and minor updates that are the same as, or newer than those of the `axeVersion` property. **Returns:** Nothing diff --git a/lib/core/public/configure.js b/lib/core/public/configure.js index 8e13f757e3..47b5de6654 100644 --- a/lib/core/public/configure.js +++ b/lib/core/public/configure.js @@ -8,6 +8,32 @@ function configureChecksRulesAndBranding(spec) { throw new Error('No audit configured'); } + if (spec.axeVersion) { + if (!/^\d+\.\d+\.\d+(-canary)?/.test(spec.axeVersion)) { + throw new Error(`Invalid configured version ${spec.axeVersion}`); + } + + let [version, canary] = spec.axeVersion.split('-'); + let [major, minor, patch] = version.split('.').map(Number); + + let [axeVersion, axeCanary] = axe.version.split('-'); + let [axeMajor, axeMinor, axePatch] = axeVersion.split('.').map(Number); + + if ( + major !== axeMajor || + axeMinor < minor || + (axeMinor === minor && axePatch < patch) || + (major === axeMajor && + minor === axeMinor && + patch === axePatch && + ((canary && axeCanary) || (canary && !axeCanary))) + ) { + throw new Error( + `Configured version ${spec.axeVersion} is not compatible with current axe version ${axe.version}` + ); + } + } + if ( spec.reporter && (typeof spec.reporter === 'function' || reporters[spec.reporter]) diff --git a/test/core/public/configure.js b/test/core/public/configure.js index ed499eee2a..ce3c10846a 100644 --- a/test/core/public/configure.js +++ b/test/core/public/configure.js @@ -2,9 +2,11 @@ describe('axe.configure', function() { 'use strict'; var fixture = document.getElementById('fixture'); + var axeVersion = axe.version; afterEach(function() { fixture.innerHTML = ''; + axe.version = axeVersion; }); beforeEach(function() { @@ -571,4 +573,122 @@ describe('axe.configure', function() { }); }); }); + + describe('given an axeVersion property', function() { + beforeEach(function() { + axe._load({}); + axe.version = '1.2.3'; + }); + + it('should not throw if version matches axe.version', function() { + assert.doesNotThrow(function fn() { + axe.configure({ + axeVersion: '1.2.3' + }); + }); + }); + + it('should not throw if patch version is less than axe.version', function() { + assert.doesNotThrow(function fn() { + axe.configure({ + axeVersion: '1.2.0' + }); + }); + }); + + it('should not throw if minor version is less than axe.version', function() { + assert.doesNotThrow(function fn() { + axe.configure({ + axeVersion: '1.1.9' + }); + }); + }); + + it('should not throw if versions match and axe has a canary version', function() { + axe.version = '1.2.3-canary.2664bae'; + assert.doesNotThrow(function fn() { + axe.configure({ + axeVersion: '1.2.3' + }); + }); + }); + + it('should throw if invalid version', function() { + assert.throws(function fn() { + axe.configure({ + axeVersion: '2' + }); + }, 'Invalid configured version 2'); + + assert.throws(function fn() { + axe.configure({ + axeVersion: '2..' + }); + }, 'Invalid configured version 2..'); + }); + + it('should throw if major version is different than axe.version', function() { + assert.throws(function fn() { + axe.configure( + { + axeVersion: '2.0.0' + }, + /^Configured version/ + ); + }); + assert.throws(function fn() { + axe.configure( + { + axeVersion: '0.1.2' + }, + /^Configured version/ + ); + }); + }); + + it('should throw if minor version is greater than axe.version', function() { + assert.throws(function fn() { + axe.configure( + { + axeVersion: '1.3.0' + }, + /^Configured version/ + ); + }); + }); + + it('should throw if patch version is greater than axe.version', function() { + assert.throws(function fn() { + axe.configure( + { + axeVersion: '1.2.9' + }, + /^Configured version/ + ); + }); + }); + + it('should throw if versions match and axeVersion has a canary version', function() { + assert.throws(function fn() { + axe.configure( + { + axeVersion: '1.2.3-canary.2664bae' + }, + /^Configured version/ + ); + }); + }); + + it('should throw if versions match and both have a canary version', function() { + axe.version = '1.2.3-canary.2664bae'; + assert.throws(function fn() { + axe.configure( + { + axeVersion: '1.2.3-canary.a5d727c' + }, + /^Configured version/ + ); + }); + }); + }); });