diff --git a/doc/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md similarity index 100% rename from doc/.github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE.md diff --git a/doc/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from doc/.github/PULL_REQUEST_TEMPLATE.md rename to .github/PULL_REQUEST_TEMPLATE.md diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000000..784c3bce07 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,24 @@ +{ + "default": true, + "MD001": false, + "MD002": false, + "MD004": false, + "MD006": false, + "MD007": false, + "MD009": false, + "MD010": false, + "MD012": false, + "MD013": false, + "MD022": false, + "MD024": false, + "MD026": false, + "MD029": false, + "MD030": false, + "MD031": false, + "MD032": false, + "MD033": false, + "MD034": false, + "MD036": false, + "MD040": false, + "MD041": false +} diff --git a/.retireignore.json b/.retireignore.json index eba3029354..a6de6668ea 100644 --- a/.retireignore.json +++ b/.retireignore.json @@ -11,6 +11,10 @@ "path": "node_modules/grunt-retire", "justification" : "Used only for testing" }, + { + "path": "node_modules/sshpk", + "justification" : "A dependency of retirejs/request" + }, { "path": "node_modules/grunt-mocha", "justification" : "Used only for testing" diff --git a/CHANGELOG.md b/CHANGELOG.md index 65dc426f17..4ac77b6274 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +## [3.0.2](https://github.com/dequelabs/axe-core/compare/v3.0.0-beta.2...v3.0.2) (2018-04-24) + + +### Bug Fixes + +* **rule:** Allow empty aria-labelledby values ([#829](https://github.com/dequelabs/axe-core/issues/829)) ([d280c5f](https://github.com/dequelabs/axe-core/commit/d280c5f)) +* Prevent color rules from crashing Chrome 66+ [#856](https://github.com/dequelabs/axe-core/issues/856) ([#861](https://github.com/dequelabs/axe-core/issues/861)) ([147b665](https://github.com/dequelabs/axe-core/commit/147b665)) +* **respondable:** Identify the current axe instance by its application name when it exists ([affd75c](https://github.com/dequelabs/axe-core/commit/affd75c)) +* **respondable:** Use the hard-coded axe.application name as default ([ab4a49f](https://github.com/dequelabs/axe-core/commit/ab4a49f)) +* **rule:** Ignore hashbang URLs for skiplinks ([#827](https://github.com/dequelabs/axe-core/issues/827)) ([e1f0c57](https://github.com/dequelabs/axe-core/commit/e1f0c57)) +* **rule:** Tag video-caption only as SC 1.2.2 ([87818e7](https://github.com/dequelabs/axe-core/commit/87818e7)) + + + ## [3.0.1](https://github.com/dequelabs/axe-core/compare/v3.0.0...v3.0.1) (2018-04-03) diff --git a/Gruntfile.js b/Gruntfile.js index 7c9c7b6090..64f5058cd5 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -17,6 +17,7 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-mocha'); grunt.loadTasks('build/tasks'); grunt.loadNpmTasks('grunt-parallel'); + grunt.loadNpmTasks('grunt-markdownlint'); var langs; if (grunt.option('lang')) { @@ -334,6 +335,18 @@ module.exports = function (grunt) { '!**/node_modules/**/*.js' ] } + }, + markdownlint: { + all: { + options: { + config: grunt.file.readJSON('.markdownlint.json') + }, + src: [ + 'README.md', + '.github/*.md', + 'doc/**/*.md' + ] + } } }); @@ -343,7 +356,7 @@ module.exports = function (grunt) { 'babel', 'concat:engine', 'uglify']); grunt.registerTask('test', ['build', 'retire', 'testconfig', 'fixture', 'connect', - 'mocha', 'parallel', 'eslint']); + 'mocha', 'parallel', 'eslint', 'markdownlint']); grunt.registerTask('ci-build', ['build', 'retire', 'testconfig', 'fixture', 'connect', 'parallel', 'eslint']); diff --git a/README.md b/README.md index fcada14574..1dd9fabf28 100644 --- a/README.md +++ b/README.md @@ -63,12 +63,17 @@ axe.run(function (err, results) { ``` ## Supported Browsers -The [aXe API](doc/API.md) supports the following browsers: +The [aXe API](doc/API.md) fully supports the following browsers: -* Internet Explorer v9, 10, 11 +* Microsoft Edge v40 and above * Google Chrome v42 and above * Mozilla Firefox v38 and above * Apple Safari v7 and above +* Internet Explorer v9, 10, 11 + +Support means that we will fix bugs and attempt to test each browser regularly. Only Firefox and Chrome are currently tested on every pull request. + +There is limited support for JSDOM. We will attempt to make all rules compatible with JSDOM but where this is not possible, we recommend turning those rules off. Currently the `color-contrast` rule is known not to work with JSDOM. ## The Accessibility Rules @@ -95,7 +100,7 @@ To update existing translation file, re-run `grunt translate --lang=`. ## Contributing -Read the [Proposing Axe-core Rules guide ](./doc/rule-proposal.md) +Read the [Proposing Axe-core Rules guide](./doc/rule-proposal.md) Read the [documentation on the architecture](./doc/developer-guide.md) diff --git a/axe.d.ts b/axe.d.ts index 9c7ff12a4a..d8f3ae8fb3 100644 --- a/axe.d.ts +++ b/axe.d.ts @@ -1,4 +1,4 @@ -// Type definitions for axe-core 3.0.1 +// Type definitions for axe-core 3.0.2 // Project: https://github.com/dequelabs/axe-core // Definitions by: Marcy Sutton @@ -13,18 +13,14 @@ declare module axe { type RunOnlyType = "rule" | "rules" | "tag" | "tags"; type RunOnlyObject = { - include?: string[], - exclude?: string[] + include?: string[] | string[][], + exclude?: string[] | string[][] } type RunCallback = (error: Error, results:AxeResults) => void; - interface ElementContext { - node?: Object, - selector?: string, - include?: any[], - exclude?: any[] - } + type ElementContext = Node | string | RunOnlyObject; + interface RunOnly { type: RunOnlyType, values?: TagValue[] | RunOnlyObject @@ -132,7 +128,7 @@ declare module axe { * @param {RunCallback} callback Optional The function to invoke when analysis is complete. * @returns {Promise|void} If the callback was not defined, aXe will return a Promise. */ - function run(context: ElementContext): Promise + function run(context?: ElementContext): Promise function run(options: RunOptions): Promise function run(callback: (error: Error, results:AxeResults) => void): void function run(context: ElementContext, callback: RunCallback): void diff --git a/bower.json b/bower.json index 9c88b5f872..0160188fd1 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "axe-core", - "version": "3.0.1", + "version": "3.0.2", "contributors": [ { "name": "David Sturley", diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index a0df1e7d8f..68e1a42261 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -15,7 +15,7 @@ | blink | Ensures <blink> elements are not used | cat.time-and-media, wcag2a, wcag222, section508, section508.22.j | true | | button-name | Ensures buttons have discernible text | cat.name-role-value, wcag2a, wcag412, section508, section508.22.a | true | | bypass | Ensures each page has at least one mechanism for a user to bypass navigation and jump straight to the content | cat.keyboard, wcag2a, wcag241, section508, section508.22.o | true | -| checkboxgroup | Ensures related <input type="checkbox"> elements have a group and that that group designation is consistent | cat.forms, best-practice | true | +| checkboxgroup | Ensures related <input type="checkbox"> elements have a group and that the group designation is consistent | cat.forms, best-practice | true | | color-contrast | Ensures the contrast between foreground and background colors meets WCAG 2 AA contrast ratio thresholds | cat.color, wcag2aa, wcag143 | true | | definition-list | Ensures <dl> elements are structured correctly | cat.structure, wcag2a, wcag131 | true | | dlitem | Ensures <dt> and <dd> elements are contained by a <dl> | cat.structure, wcag2a, wcag131 | true | @@ -65,5 +65,5 @@ | td-headers-attr | Ensure that each cell in a table using the headers refers to another cell in that table | cat.tables, wcag2a, wcag131, section508, section508.22.g | true | | th-has-data-cells | Ensure that each table header in a data table refers to data cells | cat.tables, wcag2a, wcag131, section508, section508.22.g | true | | valid-lang | Ensures lang attributes have valid values | cat.language, wcag2aa, wcag312 | true | -| video-caption | Ensures <video> elements have captions | cat.text-alternatives, wcag2a, wcag122, wcag123, section508, section508.22.a | true | +| video-caption | Ensures <video> elements have captions | cat.text-alternatives, wcag2a, wcag122, section508, section508.22.a | true | | video-description | Ensures <video> elements have audio descriptions | cat.text-alternatives, wcag2aa, wcag125, section508, section508.22.b | true | \ No newline at end of file diff --git a/lib/checks/navigation/internal-link-present.js b/lib/checks/navigation/internal-link-present.js index ad7fd17cb6..56cddbb62c 100644 --- a/lib/checks/navigation/internal-link-present.js +++ b/lib/checks/navigation/internal-link-present.js @@ -1,2 +1,4 @@ const links = axe.utils.querySelectorAll(virtualNode, 'a[href]'); -return links.some(vLink => vLink.actualNode.getAttribute('href')[0] === '#'); +return links.some(vLink => { + return /^#[^/!]/.test(vLink.actualNode.getAttribute('href')) +}); diff --git a/lib/commons/aria/attributes.js b/lib/commons/aria/attributes.js index c9d8323d5a..b444af85cf 100644 --- a/lib/commons/aria/attributes.js +++ b/lib/commons/aria/attributes.js @@ -55,7 +55,7 @@ aria.validateAttr = function (att) { * @return {Boolean} */ aria.validateAttrValue = function (node, attr) { - /*eslint complexity: ["error",15]*/ + /*eslint complexity: ["error",17]*/ 'use strict'; var matches, list, value = node.getAttribute(attr), @@ -84,6 +84,10 @@ aria.validateAttrValue = function (node, attr) { return !!(value && doc.getElementById(value)); case 'idrefs': + // exempt attributes that allow empty strings + if ((attrInfo.values && attrInfo.values.indexOf('') !== -1) && value.trim().length === 0) { + return true; + } list = axe.utils.tokenList(value); // Check if any value isn't in the list of values return list.reduce(function (result, token) { diff --git a/lib/commons/aria/index.js b/lib/commons/aria/index.js index b84460f7bc..7b09042504 100644 --- a/lib/commons/aria/index.js +++ b/lib/commons/aria/index.js @@ -87,7 +87,8 @@ lookupTable.attributes = { type: 'string' }, 'aria-labelledby': { - type: 'idrefs' + type: 'idrefs', + values: [''] }, 'aria-level': { type: 'int' diff --git a/lib/commons/dom/shadow-elements-from-point.js b/lib/commons/dom/shadow-elements-from-point.js index 84765bc10e..34c5c6abcd 100644 --- a/lib/commons/dom/shadow-elements-from-point.js +++ b/lib/commons/dom/shadow-elements-from-point.js @@ -9,11 +9,17 @@ * @param {Object} [root] Shadow root or document root * @return {Array} */ -dom.shadowElementsFromPoint = function(nodeX, nodeY, root = document) { - return root.elementsFromPoint(nodeX, nodeY) +dom.shadowElementsFromPoint = function(nodeX, nodeY, root = document, i=0) { + if (i > 999) { + throw new Error('Infinite loop detected'); + } + return Array.from(root.elementsFromPoint(nodeX, nodeY)) + // As of Chrome 66, elementFromPoint will return elements from parent trees. + // We only want to touch each tree once, so we're filtering out nodes on other trees. + .filter(nodes => dom.getRootNode(nodes) === root) .reduce((stack, elm) => { if (axe.utils.isShadowRoot(elm)) { - const shadowStack = dom.shadowElementsFromPoint(nodeX, nodeY, elm.shadowRoot); + const shadowStack = dom.shadowElementsFromPoint(nodeX, nodeY, elm.shadowRoot, i+1); stack = stack.concat(shadowStack); // filter host nodes which get included regardless of overlap // TODO: refactor multiline overlap checking inside shadow dom diff --git a/lib/core/utils/respondable.js b/lib/core/utils/respondable.js index ae00ddd8e3..ef3af5ecc0 100644 --- a/lib/core/utils/respondable.js +++ b/lib/core/utils/respondable.js @@ -11,8 +11,8 @@ * @private */ function _getSource() { - var application = 'axe', version = '', src; - if (typeof axe !== 'undefined' && axe._audit && !axe._audit.application) { + var application = 'axeAPI', version = '', src; + if (typeof axe !== 'undefined' && axe._audit && axe._audit.application) { application = axe._audit.application; } if (typeof axe !== 'undefined') { @@ -37,8 +37,8 @@ return ( // Check the version matches postedMessage._source === messageSource || // Allow free communication with axe test - postedMessage._source === 'axe.x.y.z' || - messageSource === 'axe.x.y.z' + postedMessage._source === 'axeAPI.x.y.z' || + messageSource === 'axeAPI.x.y.z' ); } return false; @@ -135,16 +135,16 @@ /** * Publishes the "respondable" message to the appropriate subscriber * @private - * @param {Event} event The event object of the postMessage - * @param {Object} data The data sent with the message - * @param {Boolean} keepalive Whether to allow multiple responses - default is false + * @param {Window} source The window from which the message originated + * @param {Object} data The data sent with the message + * @param {Boolean} keepalive Whether to allow multiple responses - default is false */ - function publish(target, data, keepalive) { + function publish(source, data, keepalive) { var topic = data.topic; var subscriber = subscribers[topic]; if (subscriber) { - var responder = createResponder(target, null, data.uuid); + var responder = createResponder(source, null, data.uuid); subscriber(data.message, keepalive, responder); } } diff --git a/lib/rules/checkboxgroup.json b/lib/rules/checkboxgroup.json index 23ac7aa02f..185b16abab 100644 --- a/lib/rules/checkboxgroup.json +++ b/lib/rules/checkboxgroup.json @@ -6,7 +6,7 @@ "best-practice" ], "metadata": { - "description": "Ensures related elements have a group and that that group designation is consistent", + "description": "Ensures related elements have a group and that the group designation is consistent", "help": "Checkbox inputs with the same name attribute value must be part of a group" }, "all": [], diff --git a/lib/rules/layout-table-matches.js b/lib/rules/layout-table-matches.js index bc1598ffde..373c7ef8fd 100644 --- a/lib/rules/layout-table-matches.js +++ b/lib/rules/layout-table-matches.js @@ -1 +1,5 @@ -return !axe.commons.table.isDataTable(node); \ No newline at end of file +var role = (node.getAttribute('role') || '').toLowerCase(); + +return !((role === 'presentation' || role === 'none') && + !axe.commons.dom.isFocusable(node)) && + !axe.commons.table.isDataTable(node); \ No newline at end of file diff --git a/lib/rules/skip-link-matches.js b/lib/rules/skip-link-matches.js index 7ee5bcbd17..24bab16507 100644 --- a/lib/rules/skip-link-matches.js +++ b/lib/rules/skip-link-matches.js @@ -1,2 +1 @@ -const href = node.getAttribute('href'); -return (href[0] === '#' && href.length > 1); +return /^#[^/!]/.test(node.getAttribute('href')); diff --git a/lib/rules/video-caption.json b/lib/rules/video-caption.json index 81470fb595..7b052f3157 100644 --- a/lib/rules/video-caption.json +++ b/lib/rules/video-caption.json @@ -6,7 +6,6 @@ "cat.text-alternatives", "wcag2a", "wcag122", - "wcag123", "section508", "section508.22.a" ], diff --git a/package.json b/package.json index 6e2bdc6f41..7850b7598f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "axe-core", "description": "Accessibility engine for automated Web UI testing", - "version": "3.0.1", + "version": "3.0.2", "license": "MPL-2.0", "engines": { "node": ">=4" @@ -78,6 +78,7 @@ "grunt-contrib-uglify": "^2.1.0", "grunt-contrib-watch": "^1.0.0", "grunt-eslint": "^20.1.0", + "grunt-markdownlint": "^1.1.2", "grunt-mocha": "^1.0.4", "grunt-parallel": "^0.5.1", "grunt-retire": "^1.0.7", diff --git a/sri-history.json b/sri-history.json index 34eac1e717..429101c6a1 100644 --- a/sri-history.json +++ b/sri-history.json @@ -102,5 +102,9 @@ "3.0.1": { "axe.js": "sha256-Vf/arxSrHppK2X5x6VgBZLJnCy8yK6P6uH99WwzQ30s=", "axe.min.js": "sha256-vMPyo7vifw5RTaVEAlnfwGFa9VyHymsNqanCsHh3Q8c=" + }, + "3.0.2": { + "axe.js": "sha256-D24i3Yy35gMxOZNTNZyQLAyL3W3wVvW1wUYakK5v1VI=", + "axe.min.js": "sha256-Hsc1oDUNhkVBP4gVUaC9jNm9t0qmLpTJzXW4uzx10bo=" } } \ No newline at end of file diff --git a/test/checks/aria/valid-attr-value.js b/test/checks/aria/valid-attr-value.js index a975e16b25..2763882d10 100644 --- a/test/checks/aria/valid-attr-value.js +++ b/test/checks/aria/valid-attr-value.js @@ -3,6 +3,7 @@ describe('aria-valid-attr-value', function () { var fixture = document.getElementById('fixture'); var checkContext = axe.testUtils.MockCheckContext(); + var fixtureSetup = axe.testUtils.fixtureSetup; afterEach(function () { fixture.innerHTML = ''; @@ -89,6 +90,17 @@ describe('aria-valid-attr-value', function () { axe.commons.aria.validateAttrValue = orig; }); + it('should allow empty strings rather than idrefs for specific attributes', function () { + fixtureSetup( + '' + + '
' + ); + var passing = fixture.querySelector('button'); + var failing = fixture.querySelector('div'); + assert.isTrue(checks['aria-valid-attr-value'].evaluate.call(checkContext, passing)); + assert.isFalse(checks['aria-valid-attr-value'].evaluate.call(checkContext, failing)); + }); + describe('options', function () { it('should exclude supplied attributes', function () { fixture.innerHTML = '
'; diff --git a/test/checks/media/frame-tested.js b/test/checks/media/frame-tested.js index fa2f9f59fd..3187f6915c 100644 --- a/test/checks/media/frame-tested.js +++ b/test/checks/media/frame-tested.js @@ -1,11 +1,8 @@ describe('frame-tested', function () { 'use strict'; - var checkContext = axe.testUtils.MockCheckContext(); - var __respondable; - var respondableCalls = []; - var checkEvaluate = checks['frame-tested'].evaluate.bind(checkContext); - var iframe; + var checkContext, __respondable, respondableCalls, iframe; + var checkEvaluate = checks['frame-tested'].evaluate before(function () { __respondable = axe.utils.respondable; @@ -16,9 +13,11 @@ describe('frame-tested', function () { document.querySelector('#fixture').appendChild(iframe); }); - afterEach(function () { + beforeEach(function () { respondableCalls = []; - checkContext.reset(); + checkContext = axe.testUtils.MockCheckContext(); + // Don't throw on async + checkContext._onAsync = function () {}; }) after(function () { @@ -26,7 +25,7 @@ describe('frame-tested', function () { }); it('correctly calls axe.utils.respondable', function () { - checkEvaluate(iframe); + checkEvaluate.call(checkContext, iframe); assert.lengthOf(respondableCalls, 1); assert.deepEqual(respondableCalls[0].slice(0,4), @@ -35,30 +34,30 @@ describe('frame-tested', function () { }); it('passes if the iframe contains axe-core', function (done) { - checkEvaluate(iframe, { timeout: 20 }); checkContext._onAsync = function (result) { assert.isTrue(result); done(); } + checkEvaluate.call(checkContext, iframe, { timeout: 20 }); // Respond to the ping respondableCalls[0][4](); }); it('fails if the iframe does not contain axe-core, and isViolation is true', function (done) { - checkEvaluate(iframe, { timeout: 10, isViolation: true }); - // Timeout after 10ms checkContext._onAsync = function (result) { assert.isFalse(result); done(); } + // Timeout after 10ms + checkEvaluate.call(checkContext, iframe, { timeout: 10, isViolation: true }); }); it('is incomplete if the iframe does not contain axe-core', function (done) { - checkEvaluate(iframe, { timeout: 10 }); - // Timeout after 10ms checkContext._onAsync = function (result) { assert.isUndefined(result); done(); } + // Timeout after 10ms + checkEvaluate.call(checkContext, iframe, { timeout: 10 }); }); }); diff --git a/test/checks/navigation/internal-link-present.js b/test/checks/navigation/internal-link-present.js index c6d372f09b..172dc2dcc2 100644 --- a/test/checks/navigation/internal-link-present.js +++ b/test/checks/navigation/internal-link-present.js @@ -9,7 +9,7 @@ describe('internal-link-present', function () { afterEach(function () { fixture.innerHTML = ''; - axe._tree = undefined; + axe._tree = undefined; checkContext.reset(); }); @@ -18,6 +18,21 @@ describe('internal-link-present', function () { assert.isTrue(checks['internal-link-present'].evaluate.apply(checkContext, params)); }); + it('should return false when a hashbang URL was used', function () { + var params = checkSetup(''); + assert.isFalse(checks['internal-link-present'].evaluate.apply(checkContext, params)); + }); + + it('should return false when a hash route URL was used', function () { + var params = checkSetup(''); + assert.isFalse(checks['internal-link-present'].evaluate.apply(checkContext, params)); + }); + + it('should return false when a hashbang + slash route URL was used', function () { + var params = checkSetup(''); + assert.isFalse(checks['internal-link-present'].evaluate.apply(checkContext, params)); + }); + it('should otherwise return false', function () { var params = checkSetup(''); assert.isFalse(checks['internal-link-present'].evaluate.apply(checkContext, params)); diff --git a/test/core/utils/respondable.js b/test/core/utils/respondable.js index d13249b72d..f15fd36827 100644 --- a/test/core/utils/respondable.js +++ b/test/core/utils/respondable.js @@ -118,7 +118,7 @@ describe('axe.utils.respondable', function () { event.initEvent('message', true, true); event.data = JSON.stringify({ _respondable: true, - _source: 'axe.2.0.0', + _source: 'axeAPI.2.0.0', topic: 'Death star', message: 'Help us Obi-Wan', uuid: mockUUID @@ -133,14 +133,14 @@ describe('axe.utils.respondable', function () { assert.isTrue(success); }); - it('should allow messages with _source axe.x.y.z', function () { + it('should allow messages with _source axeAPI.x.y.z', function () { var success = false; var event = document.createEvent('Event'); // Define that the event name is 'build'. event.initEvent('message', true, true); event.data = JSON.stringify({ _respondable: true, - _source: 'axe.x.y.z', + _source: 'axeAPI.x.y.z', topic: 'Death star', message: 'Help us Obi-Wan', uuid: mockUUID @@ -164,7 +164,7 @@ describe('axe.utils.respondable', function () { event.initEvent('message', true, true); event.data = JSON.stringify({ _respondable: true, - _source: 'axe.2.0.0', + _source: 'axeAPI.2.0.0', topic: 'Death star', message: 'Help us Obi-Wan', uuid: mockUUID @@ -189,7 +189,7 @@ describe('axe.utils.respondable', function () { event.initEvent('message', true, true); event.data = JSON.stringify({ _respondable: true, - _source: 'axe.2.0.0', + _source: 'axeAPI.2.0.0', topic: 'Death star', message: 'Help us Obi-Wan', uuid: mockUUID @@ -311,7 +311,7 @@ describe('axe.utils.respondable', function () { event.initEvent('message', true, true); event.data = JSON.stringify({ _respondable: true, - _source: 'axe.2.0.0', + _source: 'axeAPI.2.0.0', topic: 'Death star', error: { name: 'ReferenceError', @@ -340,7 +340,7 @@ describe('axe.utils.respondable', function () { event.initEvent('message', true, true); event.data = JSON.stringify({ _respondable: true, - _source: 'axe.2.0.0', + _source: 'axeAPI.2.0.0', topic: 'Death star', error: { name: 'evil', diff --git a/test/integration/full/bypass/skip-link.html b/test/integration/full/bypass/skip-link.html index 3e732b2883..1f1ad24332 100644 --- a/test/integration/full/bypass/skip-link.html +++ b/test/integration/full/bypass/skip-link.html @@ -17,7 +17,7 @@ - Skip + Skip
Test.
stuff
diff --git a/test/rule-matches/data-table-matches.js b/test/rule-matches/data-table-matches.js new file mode 100644 index 0000000000..54df9cc9be --- /dev/null +++ b/test/rule-matches/data-table-matches.js @@ -0,0 +1,52 @@ +describe('data-table-matches', function () { + 'use strict'; + + var fixture = document.getElementById('fixture'); + var fixtureSetup = axe.testUtils.fixtureSetup; + var rule; + + beforeEach(function () { + rule = axe._audit.rules.find(function (rule) { + return rule.id === 'th-has-data-cells'; + }); + }); + + afterEach(function () { + fixture.innerHTML = ''; + }); + + it('is a function', function () { + assert.isFunction(rule.matches); + }); + + it('should return false if table has role="presentation"', function () { + fixtureSetup('' + + ' ' + + ' ' + + ''); + + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'table')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('should return false if table has role="none"', function () { + fixtureSetup('' + + ' ' + + ' ' + + '
hi
hi
'); + + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'table')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('should return true if table is a data table', function () { + fixtureSetup('' + + ' ' + + ' ' + + ' ' + + '
Table caption
Heading 1Heading 2
Thing 1Thing 2
'); + + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'table')[0]; + assert.isTrue(rule.matches(vNode.actualNode, vNode)); + }); +}); \ No newline at end of file diff --git a/test/rule-matches/layout-table-matches.js b/test/rule-matches/layout-table-matches.js new file mode 100644 index 0000000000..bf403740b5 --- /dev/null +++ b/test/rule-matches/layout-table-matches.js @@ -0,0 +1,52 @@ +describe('layout-table-matches', function () { + 'use strict'; + + var fixture = document.getElementById('fixture'); + var fixtureSetup = axe.testUtils.fixtureSetup; + var rule; + + beforeEach(function () { + rule = axe._audit.rules.find(function (rule) { + return rule.id === 'layout-table'; + }); + }); + + afterEach(function () { + fixture.innerHTML = ''; + }); + + it('returns false for element that is not focusable and has presentation role', function () { + fixtureSetup('
'); + var target = fixture.querySelector('table'); + + assert.isFalse(rule.matches(target)); + }); + + it('returns false for element that is not focusable and has none role', function () { + fixtureSetup('
'); + var target = fixture.querySelector('table'); + + assert.isFalse(rule.matches(target)); + }); + + it('returns trie for element that is a table without presentation/none role', function () { + fixtureSetup('
'); + var target = fixture.querySelector('table'); + + assert.isTrue(rule.matches(target)); + }); + + it('returns true for element that is focusable and has none role', function () { + fixtureSetup('
'); + var target = fixture.querySelector('table'); + + assert.isTrue(rule.matches(target)); + }); + + it('returns true for element that is focusable and has presentation role', function () { + fixtureSetup('
'); + var target = fixture.querySelector('table'); + + assert.isTrue(rule.matches(target)); + }); +}); diff --git a/test/rule-matches/skip-link-matches.js b/test/rule-matches/skip-link-matches.js new file mode 100644 index 0000000000..0de992d5af --- /dev/null +++ b/test/rule-matches/skip-link-matches.js @@ -0,0 +1,46 @@ +describe('skip-link-matches', function () { + 'use strict'; + + var rule, link; + var fixture = document.getElementById('fixture'); + + beforeEach(function () { + rule = axe._audit.rules.find(function (rule) { + return rule.id === 'skip-link'; + }); + link = document.createElement('a') + }); + + afterEach(function () { + fixture.innerHTML = ''; + }); + + it('is a function', function () { + assert.isFunction(rule.matches); + }); + + it('returns false if the href attribute does not start with #', function () { + link.href = 'foo#bar'; + assert.isFalse(rule.matches(link)); + }); + + it('returns false if the href attribute is `#`', function () { + link.href = '#'; + assert.isFalse(rule.matches(link)); + }); + + it('returns true if the href attribute starts with #', function () { + link.href = '#foo'; + assert.isTrue(rule.matches(link)); + }); + + it('returns false if the href attribute starts with #!', function () { + link.href = '#!foo'; + assert.isFalse(rule.matches(link)); + }); + + it('returns false if the href attribute starts with #/', function () { + link.href = '#/foo'; + assert.isFalse(rule.matches(link)); + }); +}); \ No newline at end of file diff --git a/typings/axe-core/axe-core-tests.ts b/typings/axe-core/axe-core-tests.ts index a636177e89..a7ef92577d 100644 --- a/typings/axe-core/axe-core-tests.ts +++ b/typings/axe-core/axe-core-tests.ts @@ -29,7 +29,7 @@ axe.run({exclude: [$fixture[0]]}, {}, (error: Error, results: axe.AxeResults) => console.log(error || results) }) // additional configuration options -axe.run(context, {iframes: false, selectors: false, elementRef: false}, +axe.run(context, {iframes: false, selectors: false, elementRef: false}, (error: Error, results: axe.AxeResults) => { console.log(error || results.passes.length); }); @@ -45,7 +45,7 @@ axe.run(context, tagConfig, (error: Error, results: axe.AxeResults) => { }) var includeExcludeTagsRunOnly: axe.RunOnly = { type: 'tags', - value: { + values: { include: ['wcag2a', 'wcag2aa'], exclude: ['experimental'] }