From 88cd35bfb39ed0de399e17fff6755280b7369175 Mon Sep 17 00:00:00 2001 From: Marcy Sutton Date: Fri, 8 Dec 2017 16:35:38 -0800 Subject: [PATCH 1/2] chore: merge jsdoc with develop-2x --- .gitignore | 3 +- CONTRIBUTING.md | 2 +- Gruntfile.js | 12 ++-- doc/API.md | 12 +++- doc/developer-guide.md | 30 +++++++-- doc/projects.md | 3 +- doc/rule-development.md | 66 ++++++++++++++++--- lib/checks/color/color-contrast.js | 3 +- lib/checks/color/color-contrast.json | 2 +- lib/checks/color/link-in-text-block.js | 4 +- lib/checks/forms/fieldset.js | 13 ++-- lib/checks/navigation/href-no-hash.js | 2 +- lib/commons/aria/attributes.js | 14 +++- lib/commons/aria/index.js | 20 ++++-- lib/commons/aria/label.js | 5 +- lib/commons/aria/roles.js | 30 ++++++++- lib/commons/color/contrast.js | 37 ++++++++--- lib/commons/color/element-is-distinct.js | 17 ++++- lib/commons/color/get-background-color.js | 19 +++++- lib/commons/color/get-foreground-color.js | 7 +- lib/commons/color/incomplete-data.js | 16 ++++- lib/commons/color/index.js | 6 ++ lib/commons/dom/elements-below-floating.js | 4 +- lib/commons/dom/find-up.js | 8 ++- lib/commons/dom/get-element-by-reference.js | 25 +++---- lib/commons/dom/get-element-coordinates.js | 33 +++++----- lib/commons/dom/get-scroll-offset.js | 9 ++- lib/commons/dom/get-viewport-size.js | 11 ++-- lib/commons/dom/has-content.js | 13 ++-- lib/commons/dom/idrefs.js | 10 ++- lib/commons/dom/index.js | 9 ++- lib/commons/dom/is-focusable.js | 7 +- lib/commons/dom/is-html5.js | 13 +++- lib/commons/dom/is-node.js | 14 +++- lib/commons/dom/is-offscreen.js | 12 +++- lib/commons/dom/is-visible.js | 12 +++- lib/commons/dom/is-visual-content.js | 13 ++-- lib/commons/dom/visually-contains.js | 3 + lib/commons/dom/visually-overlaps.js | 3 + lib/commons/index.js | 8 +++ lib/commons/table/get-all-cells.js | 11 +++- lib/commons/table/get-cell-position.js | 11 ++-- lib/commons/table/get-headers.js | 9 ++- lib/commons/table/get-scope.js | 13 ++-- lib/commons/table/index.js | 9 ++- lib/commons/table/is-column-header.js | 13 ++-- lib/commons/table/is-data-cell.js | 9 ++- lib/commons/table/is-data-table.js | 13 ++-- lib/commons/table/is-header.js | 7 +- lib/commons/table/is-row-header.js | 13 ++-- lib/commons/table/to-grid.js | 7 +- lib/commons/table/traverse.js | 15 +++-- lib/commons/text/accessible-text.js | 12 ++-- lib/commons/text/index.js | 11 +++- lib/commons/text/sanitize.js | 11 +++- lib/commons/text/visible.js | 14 +++- lib/commons/utils/index.js | 12 +++- lib/commons/utils/to-array.js | 13 +++- lib/commons/utils/token-list.js | 13 +++- lib/commons/utils/valid-langs.js | 14 +++- lib/core/base/audit.js | 2 +- lib/core/public/plugins.js | 2 +- lib/core/utils/contains.js | 3 + lib/core/utils/find-by.js | 5 +- lib/core/utils/index.js | 9 ++- lib/core/utils/is-hidden.js | 5 +- lib/core/utils/select.js | 2 +- sri-history.json | 2 +- test/.jshintrc | 1 + test/checks/.jshintrc | 1 + test/commons/.jshintrc | 1 + test/commons/color/contrast.js | 8 +++ test/core/base/audit.js | 16 +++++ test/core/utils/send-command-to-frame.js | 22 ++++--- .../rules/aria-allowed-attr/failures.html | 2 + .../rules/aria-allowed-attr/failures.json | 4 +- .../rules/aria-allowed-attr/passes.html | 4 +- .../rules/aria-allowed-attr/passes.json | 2 +- .../aria-valid-attr-value.html | 1 + 79 files changed, 643 insertions(+), 209 deletions(-) diff --git a/.gitignore b/.gitignore index e06651b9f6..01c0ae77a4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ axe.*.js npm-shrinkwrap.json typings/axe-core/axe-core-tests.js doc/rule-descriptions.*.md -package-lock.json \ No newline at end of file +package-lock.json +.DS_Store diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0fd3b4b33e..820b999224 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Contributor License Agreement -In order to contribute, you must accept the [contributor licence agreement](https://cla-assistant.io/dequelabs/axe-core) (CLA). Acceptance of this agreement will be checked automatically and pull requests without a CLA cannot be merged. +In order to contribute, you must accept the [contributor license agreement](https://cla-assistant.io/dequelabs/axe-core) (CLA). Acceptance of this agreement will be checked automatically and pull requests without a CLA cannot be merged. ## Contribution Guidelines diff --git a/Gruntfile.js b/Gruntfile.js index 55fa15e78b..2f28d89f3b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -54,14 +54,14 @@ module.exports = function (grunt) { ] } }, - retire: { + retire: { options: { /** list of files to ignore **/ ignorefile: '.retireignore.json' //or '.retireignore.json' }, js: ['lib/*.js'], /** Which js-files to scan. **/ node: ['./'] /** Which node directories to scan (containing package.json). **/ - }, + }, clean: ['dist', 'tmp', 'axe.js', 'axe.*.js'], babel: { options: { @@ -94,15 +94,15 @@ module.exports = function (grunt) { }, concat: { engine: { + options: { + process: true + }, coreFiles: [ 'tmp/core/index.js', 'tmp/core/*/index.js', 'tmp/core/**/index.js', 'tmp/core/**/*.js' ], - options: { - process: true - }, files: langs.map(function (lang, i) { return { src: [ @@ -112,7 +112,7 @@ module.exports = function (grunt) { '<%= configure.rules.files[' + i + '].dest.auto %>', 'lib/outro.stub' ], - dest: 'axe' + lang + '.js', + dest: 'axe' + lang + '.js' }; }) }, diff --git a/doc/API.md b/doc/API.md index 26508097cd..e72e12c90d 100644 --- a/doc/API.md +++ b/doc/API.md @@ -462,7 +462,15 @@ This will either be null or an object which is an instance of Error. If you are #### Results Object -The callback function passed in as the third parameter of `axe.a11yCheck` runs on the results object. This object has two components – a passes array and a violations array. The passes array keeps track of all the passed tests, along with detailed information on each one. This leads to more efficient testing, especially when used in conjunction with manual testing, as the user can easily find out what tests have already been passed. Similarly, the violations array keeps track of all the failed tests, along with detailed information on each one. +The callback function passed in as the third parameter of `axe.run` runs on the results object. This object has four components – a `passes` array, a `violations` array, an `incomplete` array and an `inapplicable` array. + +The `passes` array keeps track of all the passed tests, along with detailed information on each one. This leads to more efficient testing, especially when used in conjunction with manual testing, as the user can easily find out what tests have already been passed. + +Similarly, the `violations` array keeps track of all the failed tests, along with detailed information on each one. + +The `incomplete` array (also referred to as the "review items") indicates which nodes could neither be determined to definitively pass or definitively fail. They are separated out in order that a user interface can display these to the user for manual review (hence the term "review items"). + +The `inapplicable` array lists all the rules for which no matching elements were found on the page. ###### `url` @@ -487,7 +495,7 @@ Each object returned in these arrays have the following properties: * `helpUrl` - URL that provides more information about the specifics of the violation. Links to a page on the Deque University site. * `id` - Unique identifier for the rule; [see the list of rules](rule-descriptions.md) * `impact` - How serious the violation is. Can be one of "minor", "moderate", "serious", or "critical" if the Rule failed or `null` if the check passed -* `tags` - Array of tags that this rule is assigned. These tags can be used in the option structure to select which rules are run ([see `axe.a11yCheck` parameters below for more information](#a11ycheck-parameters)). +* `tags` - Array of tags that this rule is assigned. These tags can be used in the option structure to select which rules are run ([see `axe.run` parameters for more information](#parameters-axerun)). * `nodes` - Array of all elements the Rule tested * `html` - Snippet of HTML of the Element * `impact` - How serious the violation is. Can be one of "minor", "moderate", "serious", or "critical" if the test failed or `null` if the check passed diff --git a/doc/developer-guide.md b/doc/developer-guide.md index 727b2db17c..c0ded55c1a 100644 --- a/doc/developer-guide.md +++ b/doc/developer-guide.md @@ -2,6 +2,16 @@ aXe runs a series of tests to check for accessibility of content and functionality on a website. A test is made up of a series of Rules which are, themselves, made up of Checks. aXe executes these Rules asynchronously and, when the Rules are finished running, runs a callback function which is passed a Result structure. Since some Rules run on the page level while others do not, tests will also run in one of two ways. If a document is specified, the page level rules will run, otherwise they will not. +1. [Getting Started](#getting-started) +1. [Architecture Overview](#architecture-overview) + 1. [Rules](#rules) + 1. [Checks](#checks) + 1. [Common Functions](#common-functions) + 1. [Core Utilities](#core-utilities) +1. [Test Utilities](#test-utilities) + 1. [Test Util Name: axe.testUtils.MockCheckContext](#test-util-name-axetestutilsmockcheckcontext) + 1. [Test Util Name: axe.testUtils.fixtureSetup](#test-util-name-axetestutilsfixturesetup) + ## Getting Started ### Environment Pre-requisites @@ -16,9 +26,9 @@ To build axe.js, simply run `grunt build`. axe.js and axe.min.js are placed int ### Running Tests -To run all tests from the command line you can run `grunt test`, which will run all unit and integration tests using PhantomJS. +To run all tests from the command line you can run `grunt test`, which will run all unit and integration tests using PhantomJS and Selenium Webdriver. -You can also load tests in any supported browser, which is helpful for debugging. Tests require a local server to run, you must first start a local server to serve files. You can use Grunt to start one by running `grunt connect watch`. Once your local server is running you can load the following pages in any browser to run tests: +You can also load tests in any supported browser, which is helpful for debugging. Tests require a local server to run, you must first start a local server to serve files. You can use Grunt to start one by running `grunt dev`. Once your local server is running you can load the following pages in any browser to run tests: 1. [Core Tests](../test/core/) @@ -78,6 +88,7 @@ Similar to Rules, Checks are defined by JSON files in the [lib/checks directory] * `messages` - `Object` These messages are displayed when the Check passes or fails * `pass` - `String` [doT.js](http://olado.github.io/doT/) template string displayed when the Check passes * `fail` - `String` [doT.js](http://olado.github.io/doT/) template string displayed when the Check fails + * `incomplete` – `String|Object` – [doT.js](http://olado.github.io/doT/) template string displayed when the Check is incomplete OR an object with `missingData` on why it returned incomplete. Refer to [rules.md](./rules.md). #### Check `evaluate` @@ -115,9 +126,20 @@ return results.filter(function (r) { }); ``` -#### Pass and Fail Templates +#### Pass, Fail and Incomplete Templates + +Occasionally, you may want to add additional information about why a Check passed, failed or returned undefined into its message. For example, the [aria-valid-attr](../lib/checks/aria/valid-attr.json) will add information about any invalid ARIA attributes to its fail message. The message uses the [doT.js](http://olado.github.io/doT/) and is compiled to a JavaScript function at build-time. In the Check message, you have access to the `CheckResult` as `it`. -Occasionally, you may want to add additional information about why a Check passed or failed into its message. For example, the [aria-valid-attr](../lib/checks/aria/valid-attr.json) will add information about any invalid ARIA attributes to its fail message. The message uses the [doT.js](http://olado.github.io/doT/) and is compiled to a JavaScript function at build-time. In the Check message, you have access to the `CheckResult` as `it`. +```javascript +// aria-valid-attr check +"messages": { + "pass": "ARIA attributes are used correctly for the defined role", + "fail": "ARIA attribute{{=it.data && it.data.length > 1 ? 's are' : ' is'}} not allowed:{{~it.data:value}} {{=value}}{{~}}" +} +``` + +See [Developing Axe-core Rules](./rule-development.md) for more information +on writing rules and checks, including incomplete results. See [Developing Axe-core Rules](./rule-development.md) for more information on writing rules and checks, including incomplete results. diff --git a/doc/projects.md b/doc/projects.md index 15b10c33a3..3340713263 100644 --- a/doc/projects.md +++ b/doc/projects.md @@ -19,7 +19,6 @@ Add your project/integration to this file and submit a pull request. 1. [Vorlon.js Remote Debugger](https://github.com/MicrosoftDX/Vorlonjs) 1. [Selenium IDE aXe Extension](https://github.com/bkardell/selenium-ide-axe) 1. [gulp-axe-webdriver](https://github.com/felixzapata/gulp-axe-webdriver) -1. [AccessLint](https://accesslint.com/) 1. [Lighthouse](https://github.com/GoogleChrome/lighthouse) 1. [Axegrinder](https://github.com/claflamme/axegrinder) 1. [Ghost-Axe](https://www.npmjs.com/package/ghost-axe) @@ -30,3 +29,5 @@ Add your project/integration to this file and submit a pull request. 1. [Rocket Validator](https://rocketvalidator.com) 1. [aXe Reports](https://github.com/louis-reed/axe-reports) 1. [aXe-TestCafe](https://github.com/helen-dikareva/axe-testcafe) +1. [Web Audit University of Nebraska-Lincoln](https://webaudit.unl.edu/) +1. [Ace, by DAISY](https://daisy.github.io/ace) diff --git a/doc/rule-development.md b/doc/rule-development.md index fd96d7db01..88ff245b39 100644 --- a/doc/rule-development.md +++ b/doc/rule-development.md @@ -33,16 +33,62 @@ The actual testing of elements in axe-core is done by checks. A rule has one or | enabled | Does the rule run by default | tags | Grouping for the rule, such as wcag2a, best-practice | metadata.description | Description of what a rule does -| metadata.help | Description of how to resolve an issue +| metadata.help | Short description of a violation, used in the aXe extension sidebar ## Check Properties -| Prop. Name | Description -|-----------------|----------------- -| id | Unique identifier for the check -| evaluate | Evaluating function, returning a boolean value -| options | Configurable value for the check -| after | Cleanup function, run after check is done -| metadata impact | "minor", "serious", "critical" -| metadata pass | Describes why the check passed -| metadata fail | Describes why the check failed +| Prop. Name | Description +|--------------------|----------------- +| id | Unique identifier for the check +| evaluate | Evaluating function, returning a boolean value +| options | Configurable value for the check +| after | Cleanup function, run after check is done +| metadata impact | "minor", "serious", "critical" +| metadata pass | Describes why the check passed +| metadata fail | Describes why the check failed +| metadata incomplete| Describes why the check didn’t complete + +Incomplete results occur when axe-core can’t produce a clear pass or fail result, +giving users the opportunity to review it manually. Incomplete messages can take +the form of a string, or an object with arbitrary keys matching the data returned +from the check. + +A pass message is required, while fail and incomplete are dependent on the check result. + +### Incomplete message string + +As one example, the audio and video caption checks return an incomplete string: +``` +messages: { + pass: 'Why the check passed', + fail: 'Why the check failed', + incomplete: 'Why the check returned undefined' +} +``` + +### Incomplete message object with missingData + +As another example, the color-contrast check returns missingData to aid in +remediation. Here’s the message format: + +``` +messages: { + pass: 'Why the check passed', + fail: 'Why the check failed', + incomplete: { + bgImage: 'The background color could not be determined due to a background image', + default: 'fallback string' + } +} +``` + +To wire up an incomplete message with a specific reason it returned undefined, +the check needs matching data. Otherwise, it will fall back to the `default` message. +Reasons are arbitrary for the check (such as 'bgImage') but they must match the +data returned: + +``` +this.data({ + missingData: 'bgImage' +}); +``` diff --git a/lib/checks/color/color-contrast.js b/lib/checks/color/color-contrast.js index 349eb8e0b1..dcd5fb7b27 100644 --- a/lib/checks/color/color-contrast.js +++ b/lib/checks/color/color-contrast.js @@ -36,7 +36,8 @@ var data = { contrastRatio: cr ? truncatedResult : undefined, fontSize: (fontSize * 72 / 96).toFixed(1) + 'pt', fontWeight: bold ? 'bold' : 'normal', - missingData: missing + missingData: missing, + expectedContrastRatio: cr.expectedContrastRatio + ':1' }; this.data(data); diff --git a/lib/checks/color/color-contrast.json b/lib/checks/color/color-contrast.json index 578a0db2c4..d552736531 100644 --- a/lib/checks/color/color-contrast.json +++ b/lib/checks/color/color-contrast.json @@ -5,7 +5,7 @@ "impact": "serious", "messages": { "pass": "Element has sufficient color contrast of {{=it.data.contrastRatio}}", - "fail": "Element has insufficient color contrast of {{=it.data.contrastRatio}} (foreground color: {{=it.data.fgColor}}, background color: {{=it.data.bgColor}}, font size: {{=it.data.fontSize}}, font weight: {{=it.data.fontWeight}})", + "fail": "Element has insufficient color contrast of {{=it.data.contrastRatio}} (foreground color: {{=it.data.fgColor}}, background color: {{=it.data.bgColor}}, font size: {{=it.data.fontSize}}, font weight: {{=it.data.fontWeight}}). Expected contrast ratio of {{=it.data.expectedContrastRatio}}", "incomplete": { "bgImage": "Element's background color could not be determined due to a background image", "bgGradient": "Element's background color could not be determined due to a background gradient", diff --git a/lib/checks/color/link-in-text-block.js b/lib/checks/color/link-in-text-block.js index 8bcf7eddae..5d26b1a9dc 100644 --- a/lib/checks/color/link-in-text-block.js +++ b/lib/checks/color/link-in-text-block.js @@ -1,5 +1,5 @@ /* global axe*/ -var color = axe.commons.color; +const { color } = axe.commons; function getContrast(color1, color2) { var c1lum = color1.getRelativeLuminance(); @@ -7,7 +7,7 @@ function getContrast(color1, color2) { return (Math.max(c1lum, c2lum) + 0.05) / (Math.min(c1lum, c2lum) + 0.05); } -var blockLike = ['block', 'list-item', 'table', 'flex', 'grid', 'inline-block']; +const blockLike = ['block', 'list-item', 'table', 'flex', 'grid', 'inline-block']; function isBlock(elm) { var display = window.getComputedStyle(elm).getPropertyValue('display'); return (blockLike.indexOf(display) !== -1 || diff --git a/lib/checks/forms/fieldset.js b/lib/checks/forms/fieldset.js index 8800412c91..bad8c3c407 100644 --- a/lib/checks/forms/fieldset.js +++ b/lib/checks/forms/fieldset.js @@ -63,15 +63,20 @@ function runCheck(element) { if (matchingNodes.length < 2) { return true; } - var fieldset = axe.commons.dom.findUp(element, 'fieldset'); - var group = axe.commons.dom.findUp(element, '[role="group"]' + (node.type === 'radio' ? ',[role="radiogroup"]' : '')); + const fieldset = axe.commons.dom.findUp(element, 'fieldset'); + const group = axe.commons.dom.findUp(element, '[role="group"]' + + (node.type === 'radio' ? ',[role="radiogroup"]' : '')); + if (!group && !fieldset) { failureCode = 'no-group'; self.relatedNodes(spliceCurrentNode(matchingNodes, element)); return false; - } - return fieldset ? checkFieldset(fieldset, name) : checkARIAGroup(group, name); + } else if (fieldset) { + return checkFieldset(fieldset, name); + } else { + return checkARIAGroup(group, name); + } } var data = { diff --git a/lib/checks/navigation/href-no-hash.js b/lib/checks/navigation/href-no-hash.js index fff3a1699a..d89cc23034 100644 --- a/lib/checks/navigation/href-no-hash.js +++ b/lib/checks/navigation/href-no-hash.js @@ -1,6 +1,6 @@ var href = node.getAttribute('href'); -if(href === '#'){ +if (href === '#') { return false; } diff --git a/lib/commons/aria/attributes.js b/lib/commons/aria/attributes.js index 00481cc040..acbc00f10a 100644 --- a/lib/commons/aria/attributes.js +++ b/lib/commons/aria/attributes.js @@ -2,6 +2,9 @@ /** * Get required attributes for a given role + * @method requiredAttr + * @memberof axe.commons.aria + * @instance * @param {String} role The role to check * @return {Array} */ @@ -14,6 +17,9 @@ aria.requiredAttr = function (role) { /** * Get allowed attributes for a given role + * @method allowedAttr + * @memberof axe.commons.aria + * @instance * @param {String} role The role to check * @return {Array} */ @@ -28,6 +34,9 @@ aria.allowedAttr = function (role) { /** * Check if an aria- attribute name is valid + * @method validateAttr + * @memberof axe.commons.aria + * @instance * @param {String} att The attribute name * @return {Boolean} */ @@ -38,6 +47,9 @@ aria.validateAttr = function (att) { /** * Validate the value of an ARIA attribute + * @method validateAttrValue + * @memberof axe.commons.aria + * @instance * @param {HTMLElement} node The element to check * @param {String} attr The name of the attribute * @return {Boolean} @@ -59,7 +71,7 @@ aria.validateAttrValue = function (node, attr) { case 'nmtoken': return (typeof value === 'string' && attrInfo.values.indexOf(value.toLowerCase()) !== -1); - case 'nmtokens': + case 'nmtokens': 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 d0752ac083..18b4168834 100644 --- a/lib/commons/aria/index.js +++ b/lib/commons/aria/index.js @@ -1,3 +1,9 @@ +/** + * Namespace for aria-related utilities. + * @namespace commons.aria + * @memberof axe + */ + var aria = commons.aria = {}, lookupTables = aria._lut = {}; @@ -253,7 +259,7 @@ lookupTables.role = { 'columnheader': { type: 'structure', attributes: { - allowed: ['aria-colindex', 'aria-colspan', 'aria-expanded', 'aria-rowindex', 'aria-rowspan', + allowed: ['aria-colindex', 'aria-colspan', 'aria-expanded', 'aria-rowindex', 'aria-rowspan', 'aria-required', 'aria-readonly', 'aria-selected', 'aria-sort'] }, owned: null, @@ -344,7 +350,7 @@ lookupTables.role = { attributes: { allowed: ['aria-expanded'] }, - owned: { + owned: { one: ['article'] }, nameFrom: ['author'], @@ -363,7 +369,8 @@ lookupTables.role = { 'grid': { type: 'composite', attributes: { - allowed: ['aria-level', 'aria-multiselectable', 'aria-readonly', 'aria-activedescendant', 'aria-expanded'] + allowed: ['aria-activedescendant', 'aria-expanded', 'aria-colcount', 'aria-level', + 'aria-multiselectable', 'aria-readonly', 'aria-rowcount'] }, owned: { one: ['rowgroup', 'row'] @@ -375,7 +382,7 @@ lookupTables.role = { 'gridcell': { type: 'widget', attributes: { - allowed: ['aria-colindex', 'aria-colspan', 'aria-expanded', 'aria-rowindex', + allowed: ['aria-colindex', 'aria-colspan', 'aria-expanded', 'aria-rowindex', 'aria-rowspan', 'aria-selected', 'aria-readonly', 'aria-required'] }, owned: null, @@ -648,7 +655,8 @@ lookupTables.role = { 'row': { type: 'structure', attributes: { - allowed: ['aria-activedescendant', 'aria-colcount', 'aria-expanded', 'aria-level', 'aria-selected', 'aria-rowcount', 'aria-rowindex'] + allowed: ['aria-activedescendant', 'aria-colindex', 'aria-expanded', + 'aria-level', 'aria-selected', 'aria-rowindex'] }, owned: { one: ['cell', 'columnheader', 'rowheader', 'gridcell'] @@ -672,7 +680,7 @@ lookupTables.role = { 'rowheader': { type: 'structure', attributes: { - allowed: ['aria-colindex', 'aria-colspan', 'aria-expanded', 'aria-rowindex', 'aria-rowspan', + allowed: ['aria-colindex', 'aria-colspan', 'aria-expanded', 'aria-rowindex', 'aria-rowspan', 'aria-required', 'aria-readonly', 'aria-selected', 'aria-sort'] }, owned: null, diff --git a/lib/commons/aria/label.js b/lib/commons/aria/label.js index 4fac7afe34..2f0b90cc12 100644 --- a/lib/commons/aria/label.js +++ b/lib/commons/aria/label.js @@ -1,7 +1,10 @@ -/*global aria, dom, text */ +/* global aria, dom, text */ /** * Gets the accessible ARIA label text of a given element * @see http://www.w3.org/WAI/PF/aria/roles#namecalculation + * @method label + * @memberof axe.commons.aria + * @instance * @param {HTMLElement} node The element to test * @return {Mixed} String of visible text, or `null` if no label is found */ diff --git a/lib/commons/aria/roles.js b/lib/commons/aria/roles.js index bd822e1667..56bd52b4b8 100644 --- a/lib/commons/aria/roles.js +++ b/lib/commons/aria/roles.js @@ -1,7 +1,10 @@ -/*global aria, lookupTables, axe */ +/* global aria, lookupTables, axe */ /** * Check if a given role is valid + * @method isValidRole + * @memberof axe.commons.aria + * @instance * @param {String} role The role to check * @return {Boolean} */ @@ -15,8 +18,11 @@ aria.isValidRole = function (role) { }; /** - * Get the roles that get name from contents - * @return {Array} Array of roles that match the type + * Get the roles that get name from the element's contents + * @method getRolesWithNameFromContents + * @memberof axe.commons.aria + * @instance + * @return {Array} Array of roles that match the type */ aria.getRolesWithNameFromContents = function () { return Object.keys(lookupTables.role).filter(function (r) { @@ -27,6 +33,9 @@ aria.getRolesWithNameFromContents = function () { /** * Get the roles that have a certain "type" + * @method getRolesByType + * @memberof axe.commons.aria + * @instance * @param {String} roleType The roletype to check * @return {Array} Array of roles that match the type */ @@ -38,6 +47,9 @@ aria.getRolesByType = function (roleType) { /** * Get the "type" of role; either widget, composite, abstract, landmark or `null` + * @method getRoleType + * @memberof axe.commons.aria + * @instance * @param {String} role The role to check * @return {Mixed} String if a matching role and its type are found, otherwise `null` */ @@ -49,6 +61,9 @@ aria.getRoleType = function (role) { /** * Get the required owned (children) roles for a given role + * @method requiredOwned + * @memberof axe.commons.aria + * @instance * @param {String} role The role to check * @return {Mixed} Either an Array of required owned elements or `null` if there are none */ @@ -65,6 +80,9 @@ aria.requiredOwned = function (role) { /** * Get the required context (parent) roles for a given role + * @method requiredContext + * @memberof axe.commons.aria + * @instance * @param {String} role The role to check * @return {Mixed} Either an Array of required context elements or `null` if there are none */ @@ -81,6 +99,9 @@ aria.requiredContext = function (role) { /** * Get a list of CSS selectors of nodes that have an implicit role + * @method implicitNodes + * @memberof axe.commons.aria + * @instance * @param {String} role The role to check * @return {Mixed} Either an Array of CSS selectors or `null` if there are none */ @@ -98,6 +119,9 @@ aria.implicitNodes = function (role) { /** * Get the implicit role for a given node + * @method implicitRole + * @memberof axe.commons.aria + * @instance * @param {HTMLElement} node The node to test * @return {Mixed} Either the role or `null` if there is none */ diff --git a/lib/commons/color/contrast.js b/lib/commons/color/contrast.js index 9a53be83ee..4717fc4a32 100644 --- a/lib/commons/color/contrast.js +++ b/lib/commons/color/contrast.js @@ -1,7 +1,8 @@ -/*global color */ +/* global color */ /** - * @constructor + * @class Color + * @memberof axe.commons.color * @param {number} red * @param {number} green * @param {number} blue @@ -22,6 +23,9 @@ color.Color = function (red, green, blue, alpha) { /** * Provide the hex string value for the color + * @method toHexString + * @memberof axe.commons.color.Color + * @instance * @return {string} */ this.toHexString = function () { @@ -38,6 +42,9 @@ color.Color = function (red, green, blue, alpha) { /** * Set the color value based on a CSS RGB/RGBA string + * @method parseRgbString + * @memberof axe.commons.color.Color + * @instance * @param {string} rgb The string value */ this.parseRgbString = function (colorString) { @@ -72,6 +79,9 @@ color.Color = function (red, green, blue, alpha) { /** * Get the relative luminance value * using algorithm from http://www.w3.org/WAI/GL/wiki/Relative_luminance + * @method getRelativeLuminance + * @memberof axe.commons.color.Color + * @instance * @return {number} The luminance value, ranges from 0 to 1 */ this.getRelativeLuminance = function () { @@ -89,9 +99,12 @@ color.Color = function (red, green, blue, alpha) { /** * Combine the two given color according to alpha blending. - * @param {Color} fgColor - * @param {Color} bgColor - * @return {Color} + * @method flattenColors + * @memberof axe.commons.color.Color + * @instance + * @param {Color} fgColor Foreground color + * @param {Color} bgColor Background color + * @return {Color} Blended color */ color.flattenColors = function (fgColor, bgColor) { var alpha = fgColor.alpha; @@ -105,6 +118,9 @@ color.flattenColors = function (fgColor, bgColor) { /** * Get the contrast of two colors + * @method getContrast + * @memberof axe.commons.color.Color + * @instance * @param {Color} bgcolor Background color * @param {Color} fgcolor Foreground color * @return {number} The contrast ratio @@ -124,19 +140,24 @@ color.getContrast = function (bgColor, fgColor) { /** * Check whether certain text properties meet WCAG contrast rules + * @method hasValidContrastRatio + * @memberof axe.commons.color.Color + * @instance * @param {Color} bgcolor Background color * @param {Color} fgcolor Foreground color * @param {number} fontSize Font size of text, in pixels * @param {boolean} isBold Whether the text is bold - * @return {{isValid: boolean, contrastRatio: number}} + * @return {{isValid: boolean, contrastRatio: number, expectedContrastRatio: number}} */ color.hasValidContrastRatio = function (bg, fg, fontSize, isBold) { var contrast = color.getContrast(bg, fg); var isSmallFont = (isBold && (Math.ceil(fontSize * 72) / 96) < 14) || (!isBold && (Math.ceil(fontSize * 72) / 96) < 18); + var expectedContrastRatio = isSmallFont ? 4.5 : 3; return { - isValid: (isSmallFont && contrast >= 4.5) || (!isSmallFont && contrast >= 3), - contrastRatio: contrast + isValid: contrast > expectedContrastRatio, + contrastRatio: contrast, + expectedContrastRatio: expectedContrastRatio }; }; diff --git a/lib/commons/color/element-is-distinct.js b/lib/commons/color/element-is-distinct.js index 343960bd32..dac6264f34 100644 --- a/lib/commons/color/element-is-distinct.js +++ b/lib/commons/color/element-is-distinct.js @@ -1,5 +1,11 @@ -/*global color */ +/* global color */ +/** + * Creates a string array of fonts for given CSSStyleDeclaration object + * @private + * @param {Object} style CSSStyleDeclaration object + * @return {Array} + */ function _getFonts(style) { return style.getPropertyValue('font-family') .split(/[,;]/g) @@ -8,6 +14,15 @@ function _getFonts(style) { }); } +/** + * Determine if the text content of two nodes is styled in a way that they can be distinguished without relying on color + * @method elementIsDistinct + * @memberof axe.commons.color + * @instance + * @param {HTMLElement} node The element to check + * @param {HTMLElement} ancestorNode The ancestor node element to check + * @return {Boolean} + */ function elementIsDistinct(node, ancestorNode) { var nodeStyle = window.getComputedStyle(node); diff --git a/lib/commons/color/get-background-color.js b/lib/commons/color/get-background-color.js index a64a95a0fc..f439ebb634 100644 --- a/lib/commons/color/get-background-color.js +++ b/lib/commons/color/get-background-color.js @@ -138,7 +138,7 @@ function includeMissingElements(elmStack, elm) { // found an ancestor not in elmStack, and it overlaps let overlaps = axe.commons.dom.visuallyOverlaps(elm.getBoundingClientRect(), ancestorMatch); if (overlaps) { - // if target is in the elementMap, use its position. + // if target is in the elementMap, use its position. bgNodes.splice(tagArray.indexOf(candidate) + 1, 0, ancestorMatch); } } @@ -180,8 +180,12 @@ function sortPageBackground(elmStack) { } /** - * Get all elements rendered underneath the current element, - * in the order in which it is rendered + * Get all elements rendered underneath the current element, In the order they are displayed (front to back) + * @method getBackgroundStack + * @memberof axe.commons.color + * @instance + * @param {Element} elm + * @return {Array} */ color.getBackgroundStack = function(elm) { let rect = elm.getBoundingClientRect(); @@ -214,6 +218,15 @@ color.getBackgroundStack = function(elm) { return elmIndex !== -1 ? elmStack : null; }; +/** + * Returns background color for element + * Uses color.getBackgroundStack() to get all elements rendered underneath the current element to + * help determine the background color. + * @param {Element} elm Element to determine background color + * @param {Array} [bgElms=[]] [description] + * @param {Boolean} [noScroll=false] [description] + * @return {Color} [description] + */ color.getBackgroundColor = function(elm, bgElms = [], noScroll = false) { if(noScroll !== true) { // Avoid scrolling overflow:hidden containers, by only aligning to top diff --git a/lib/commons/color/get-foreground-color.js b/lib/commons/color/get-foreground-color.js index f001fe3f5e..427880ae65 100644 --- a/lib/commons/color/get-foreground-color.js +++ b/lib/commons/color/get-foreground-color.js @@ -1,11 +1,14 @@ -/*global axe, color */ +/* global axe, color */ /** * Returns the flattened foreground color of an element, or null if it can't be determined because * of transparency + * @method getForegroundColor + * @memberof axe.commons.color + * @instance * @param {Element} node * @param {Boolean} noScroll (default false) - * @return {Color} + * @return {Color|null} */ color.getForegroundColor = function (node, noScroll) { var nodeStyle = window.getComputedStyle(node); diff --git a/lib/commons/color/incomplete-data.js b/lib/commons/color/incomplete-data.js index 288f61457d..671457bb08 100644 --- a/lib/commons/color/incomplete-data.js +++ b/lib/commons/color/incomplete-data.js @@ -2,14 +2,20 @@ /** * API for handling incomplete color data + * @namespace axe.commons.color.incompleteData + * @inner */ + color.incompleteData = (function() { var data = {}; return { /** * Store incomplete data by key with a string value - * @param {String} key Identifier for missing data point (fgColor, bgColor, etc.) - * @param {String} reason Missing data reason to match message template + * @method set + * @memberof axe.commons.color.incompleteData + * @instance + * @param {String} key Identifier for missing data point (fgColor, bgColor, etc.) + * @param {String} reason Missing data reason to match message template */ set: function(key, reason) { if (typeof key !== 'string') { @@ -22,6 +28,9 @@ color.incompleteData = (function() { }, /** * Get incomplete data by key + * @method get + * @memberof axe.commons.color.incompleteData + * @instance * @param {String} key Identifier for missing data point (fgColor, bgColor, etc.) * @return {String} String for reason we couldn't tell */ @@ -30,6 +39,9 @@ color.incompleteData = (function() { }, /** * Clear incomplete data on demand + * @method clear + * @memberof axe.commons.color.incompleteData + * @instance */ clear: function() { data = {}; diff --git a/lib/commons/color/index.js b/lib/commons/color/index.js index b53233e96c..5b62fcb916 100644 --- a/lib/commons/color/index.js +++ b/lib/commons/color/index.js @@ -1,2 +1,8 @@ +/** + * Namespace for color-related utilities. + * @namespace commons.color + * @memberof axe + */ + var color = {}; commons.color = color; diff --git a/lib/commons/dom/elements-below-floating.js b/lib/commons/dom/elements-below-floating.js index 8a20481504..8368b51224 100644 --- a/lib/commons/dom/elements-below-floating.js +++ b/lib/commons/dom/elements-below-floating.js @@ -2,7 +2,9 @@ /** * Reduce an array of elements to only those that are below a 'floating' element. - * + * @method reduceToElementsBelowFloating + * @memberof axe.commons.dom + * @instance * @param {Array} elements * @param {Element} targetNode * @returns {Array} diff --git a/lib/commons/dom/find-up.js b/lib/commons/dom/find-up.js index db5c6b2ce1..b1e4a07824 100644 --- a/lib/commons/dom/find-up.js +++ b/lib/commons/dom/find-up.js @@ -1,11 +1,13 @@ -/*global dom, axe */ +/* global dom, axe */ /** * recusively walk up the DOM, checking for a node which matches a selector * * **WARNING:** this should be used sparingly, as it's not even close to being performant - * + * @method findUp + * @memberof axe.commons.dom + * @instance * @param {HTMLElement|String} element The starting HTMLElement - * @param {String} selector The selector for the HTMLElement + * @param {String} target The selector for the HTMLElement * @return {HTMLElement|null} Either the matching HTMLElement or `null` if there was no match */ dom.findUp = function (element, target) { diff --git a/lib/commons/dom/get-element-by-reference.js b/lib/commons/dom/get-element-by-reference.js index 44027aeda3..fb1a552718 100644 --- a/lib/commons/dom/get-element-by-reference.js +++ b/lib/commons/dom/get-element-by-reference.js @@ -1,26 +1,29 @@ -/*global dom */ +/* global dom */ +/** + * Returns a reference to the element matching the attr URL fragment value + * @method getElementByReference + * @memberof axe.commons.dom + * @instance + * @param {Element} node + * @param {String} attr Attribute name (href) + * @return {Element} + */ dom.getElementByReference = function (node, attr) { - 'use strict'; - - var candidate, - fragment = node.getAttribute(attr), - doc = document; + let fragment = node.getAttribute(attr); if (fragment && fragment.charAt(0) === '#') { fragment = fragment.substring(1); - candidate = doc.getElementById(fragment); + let candidate = document.getElementById(fragment); if (candidate) { return candidate; } - candidate = doc.getElementsByName(fragment); + candidate = document.getElementsByName(fragment); if (candidate.length) { return candidate[0]; } - } - return null; -}; \ No newline at end of file +}; diff --git a/lib/commons/dom/get-element-coordinates.js b/lib/commons/dom/get-element-coordinates.js index 35f8045b69..38a125b1cb 100644 --- a/lib/commons/dom/get-element-coordinates.js +++ b/lib/commons/dom/get-element-coordinates.js @@ -1,22 +1,24 @@ -/*global dom */ +/* global dom */ + /** * Get the coordinates of the element passed into the function relative to the document - * - * #### Returns - * - * Returns a `Object` with the following properties, which + * @method getElementCoordinates + * @memberof axe.commons.dom + * @instance + * @param {HTMLElement} element The HTMLElement + * @return {elementObj} elementObj Returns a `Object` with the following properties, which * each hold a value representing the pixels for each of the - * respective coordinates: - * - * - `top` - * - `right` - * - `bottom` - * - `left` - * - `width` - * - `height` - * - * @param {HTMLElement} el The HTMLElement */ + /** + * @typedef elementObj + * @type {Object} + * @property {Number} top The top coordinate of the element + * @property {Number} right The right coordinate of the element + * @property {Number} bottom The bottom coordinate of the element + * @property {Number} left The left coordinate of the element + * @property {Number} width The width of the element + * @property {Number} height The height of the element + */ dom.getElementCoordinates = function (element) { 'use strict'; @@ -30,7 +32,6 @@ dom.getElementCoordinates = function (element) { right: coords.right + xOffset, bottom: coords.bottom + yOffset, left: coords.left + xOffset, - width: coords.right - coords.left, height: coords.bottom - coords.top }; diff --git a/lib/commons/dom/get-scroll-offset.js b/lib/commons/dom/get-scroll-offset.js index 1b920e41b2..ecf53d9cfb 100644 --- a/lib/commons/dom/get-scroll-offset.js +++ b/lib/commons/dom/get-scroll-offset.js @@ -1,7 +1,10 @@ -/*global dom */ +/* global dom */ + /** * Get the scroll offset of the document passed in - * + * @method getScrollOffset + * @memberof axe.commons.dom + * @instance * @param {Document} element The element to evaluate, defaults to document * @return {Object} Contains the attributes `x` and `y` which contain the scroll offsets */ @@ -27,4 +30,4 @@ dom.getScrollOffset = function (element) { left: element.scrollLeft, top: element.scrollTop }; -}; \ No newline at end of file +}; diff --git a/lib/commons/dom/get-viewport-size.js b/lib/commons/dom/get-viewport-size.js index 0fd2fe9b10..dd310ee79a 100644 --- a/lib/commons/dom/get-viewport-size.js +++ b/lib/commons/dom/get-viewport-size.js @@ -1,9 +1,10 @@ -/*global dom */ +/* global dom */ /** * Gets the width and height of the viewport; used to calculate the right and bottom boundaries of the viewable area. - * - * @api private - * @param {Object} window The `window` object that should be measured + * @method getViewportSize + * @memberof axe.commons.dom + * @instance + * @param {Object} win The `window` object that should be measured * @return {Object} Object with the `width` and `height` of the viewport */ dom.getViewportSize = function (win) { @@ -34,4 +35,4 @@ dom.getViewportSize = function (win) { width: body.clientWidth, height: body.clientHeight }; -}; \ No newline at end of file +}; diff --git a/lib/commons/dom/has-content.js b/lib/commons/dom/has-content.js index 0b1298112f..ffd95b1ae9 100644 --- a/lib/commons/dom/has-content.js +++ b/lib/commons/dom/has-content.js @@ -1,10 +1,13 @@ -/*global dom, aria */ +/* global dom, aria */ /** - * Check that the element has visible content - * in the form of either text, an aria-label or visual content such as image + * Check that the element has visible content in the form of either text, + * an aria-label or visual content such as image * - * @param DOMElement - * @param [DOMElement] (optional) Items to ignore as content + * @method hasContent + * @memberof axe.commons.dom + * @instance + * @param {DOMNode} elm DOMNode element to check + * @param {Array} (optional) skipItems Items to ignore as content * @return boolean */ dom.hasContent = function hasContent(elm, skipItems = []) { diff --git a/lib/commons/dom/idrefs.js b/lib/commons/dom/idrefs.js index be0ef6d6b7..05d306e3fe 100644 --- a/lib/commons/dom/idrefs.js +++ b/lib/commons/dom/idrefs.js @@ -1,10 +1,14 @@ /*global dom, axe */ /** - * Get elements referenced via a space-separated token attribute; it will insert `null` for any Element that is not found + * Get elements referenced via a space-separated token attribute; + * it will insert `null` for any Element that is not found + * @method idrefs + * @memberof axe.commons.dom + * @instance * @param {HTMLElement} node * @param {String} attr The name of attribute - * @return {Array} Array of elements (or `null` if not found) + * @return {Array|null} Array of elements (or `null` if not found) */ dom.idrefs = function (node, attr) { 'use strict'; @@ -22,4 +26,4 @@ dom.idrefs = function (node, attr) { } return result; -}; \ No newline at end of file +}; diff --git a/lib/commons/dom/index.js b/lib/commons/dom/index.js index d83331fe53..e3bda7cf4e 100644 --- a/lib/commons/dom/index.js +++ b/lib/commons/dom/index.js @@ -1,2 +1,9 @@ -/*exported dom */ +/* exported dom */ + +/** + * Namespace for dom-related utilities. + * @namespace dom + * @memberof axe.commons + */ + var dom = commons.dom = {}; diff --git a/lib/commons/dom/is-focusable.js b/lib/commons/dom/is-focusable.js index ba3abce134..5735cbe877 100644 --- a/lib/commons/dom/is-focusable.js +++ b/lib/commons/dom/is-focusable.js @@ -1,8 +1,11 @@ -/*global dom */ +/* global dom */ /* jshint maxcomplexity: 20 */ /** * Determines if an element is focusable - * @param {HTMLelement} element The HTMLelement + * @method isFocusable + * @memberof axe.commons.dom + * @instance + * @param {HTMLElement} el The HTMLElement * @return {Boolean} The element's focusability status */ diff --git a/lib/commons/dom/is-html5.js b/lib/commons/dom/is-html5.js index 52e8bcee34..d9fabfa64d 100644 --- a/lib/commons/dom/is-html5.js +++ b/lib/commons/dom/is-html5.js @@ -1,8 +1,17 @@ -/*global dom */ +/* global dom */ + +/** + * Determines if a document node is HTML 5 + * @method isHTML5 + * @memberof axe.commons.dom + * @instance + * @param {Node} doc + * @return {Boolean} + */ dom.isHTML5 = function (doc) { var node = doc.doctype; if (node === null) { return false; } return node.name === 'html' && !node.publicId && !node.systemId; -}; \ No newline at end of file +}; diff --git a/lib/commons/dom/is-node.js b/lib/commons/dom/is-node.js index bacd8f914d..5207a38196 100644 --- a/lib/commons/dom/is-node.js +++ b/lib/commons/dom/is-node.js @@ -1,5 +1,13 @@ -/*global dom */ -dom.isNode = function (candidate) { +/* global dom */ +/** + * Determines if element is an instance of Node + * @method isNode + * @memberof axe.commons.dom + * @instance + * @param {Element} element + * @return {Boolean} + */ +dom.isNode = function (element) { 'use strict'; - return candidate instanceof Node; + return element instanceof Node; }; diff --git a/lib/commons/dom/is-offscreen.js b/lib/commons/dom/is-offscreen.js index a27cb9688c..bf1baddf8f 100644 --- a/lib/commons/dom/is-offscreen.js +++ b/lib/commons/dom/is-offscreen.js @@ -1,5 +1,13 @@ -/*global dom */ - +/* global dom */ + +/** + * Determines if element is off screen + * @method isOffscreen + * @memberof axe.commons.dom + * @instance + * @param {Element} element + * @return {Boolean} + */ dom.isOffscreen = function (element) { 'use strict'; var noParentScrolled = function (element, offset) { diff --git a/lib/commons/dom/is-visible.js b/lib/commons/dom/is-visible.js index 91d1d332d3..b2014dce75 100644 --- a/lib/commons/dom/is-visible.js +++ b/lib/commons/dom/is-visible.js @@ -1,8 +1,11 @@ -/*global dom */ -/*jshint maxcomplexity: 11 */ +/* global dom */ +/* jshint maxcomplexity: 11 */ /** * Determines if an element is hidden with the clip rect technique + * @method isClipped + * @memberof axe.commons.dom + * @private * @param {String} clip Computed property value of clip * @return {Boolean} */ @@ -20,9 +23,12 @@ function isClipped(clip) { /** * Determine whether an element is visible - * + * @method isVisible + * @memberof axe.commons.dom + * @instance * @param {HTMLElement} el The HTMLElement * @param {Boolean} screenReader When provided, will evaluate visibility from the perspective of a screen reader + * @param {Boolean} recursed * @return {Boolean} The element's visibilty status */ dom.isVisible = function (el, screenReader, recursed) { diff --git a/lib/commons/dom/is-visual-content.js b/lib/commons/dom/is-visual-content.js index 133dfacfad..c7e99dfaed 100644 --- a/lib/commons/dom/is-visual-content.js +++ b/lib/commons/dom/is-visual-content.js @@ -8,16 +8,19 @@ const visualRoles = [ /** * Check if an element is an inherently visual element - * @param {object} candidate The node to check + * @method isVisualContent + * @memberof axe.commons.dom + * @instance + * @param {Element} element The element to check * @return {Boolean} */ -dom.isVisualContent = function (candidate) { - const role = candidate.getAttribute('role'); +dom.isVisualContent = function (element) { + const role = element.getAttribute('role'); if (role) { return (visualRoles.indexOf(role) !== -1); } - switch (candidate.tagName.toUpperCase()) { + switch (element.tagName.toUpperCase()) { case 'IMG': case 'IFRAME': case 'OBJECT': @@ -34,7 +37,7 @@ dom.isVisualContent = function (candidate) { case 'METER': return true; case 'INPUT': - return candidate.type !== 'hidden'; + return element.type !== 'hidden'; default: return false; } diff --git a/lib/commons/dom/visually-contains.js b/lib/commons/dom/visually-contains.js index 158892e691..d70216e4a5 100644 --- a/lib/commons/dom/visually-contains.js +++ b/lib/commons/dom/visually-contains.js @@ -4,6 +4,9 @@ /** * Checks whether a parent element visually contains its child, either directly or via scrolling. * Assumes that |parent| is an ancestor of |node|. + * @method visuallyContains + * @memberof axe.commons.dom + * @instance * @param {Element} node * @param {Element} parent * @return {boolean} True if node is visually contained within parent diff --git a/lib/commons/dom/visually-overlaps.js b/lib/commons/dom/visually-overlaps.js index 9b2f4d1388..87c035b5ad 100644 --- a/lib/commons/dom/visually-overlaps.js +++ b/lib/commons/dom/visually-overlaps.js @@ -3,6 +3,9 @@ /** * Checks whether a parent element visually overlaps a rectangle, either directly or via scrolling. + * @method visuallyOverlaps + * @memberof axe.commons.dom + * @instance * @param {DOMRect} rect * @param {Element} parent * @return {boolean} True if rect is visually contained within parent diff --git a/lib/commons/index.js b/lib/commons/index.js index 972e62e9ad..0c99f643fc 100644 --- a/lib/commons/index.js +++ b/lib/commons/index.js @@ -1,2 +1,10 @@ /*exported commons */ +/** @namespace axe */ + +/** + * Namespace for axe common methods. + * @namespace commons + * @memberof axe + */ + var commons = {}; diff --git a/lib/commons/table/get-all-cells.js b/lib/commons/table/get-all-cells.js index 267c2798b8..6ecc292bdd 100644 --- a/lib/commons/table/get-all-cells.js +++ b/lib/commons/table/get-all-cells.js @@ -1,4 +1,13 @@ /* global table */ + +/** + * Returns all cells contained in given HTMLTableElement + * @method getAllCells + * @memberof axe.commons.table + * @instance + * @param {HTMLTableElement} tableElm Table Element to get cells from + * @return {Array} + */ table.getAllCells = function (tableElm) { var rowIndex, cellIndex, rowLength, cellLength; var cells = []; @@ -8,4 +17,4 @@ table.getAllCells = function (tableElm) { } } return cells; -}; \ No newline at end of file +}; diff --git a/lib/commons/table/get-cell-position.js b/lib/commons/table/get-cell-position.js index d587b07cca..c942365401 100644 --- a/lib/commons/table/get-cell-position.js +++ b/lib/commons/table/get-cell-position.js @@ -1,9 +1,12 @@ -/*global table, dom */ +/* global table, dom */ /** * Get the x, y coordinates of a table cell; normalized for rowspan and colspan - * @param {HTMLTableCelLElement} cell The table cell of which to get the position - * @return {Object} Object with `x` and `y` properties of the coordinates + * @method getCellPosition + * @memberof axe.commons.table + * @instance + * @param {HTMLTableCellElement} cell The table cell of which to get the position + * @return {Object} Object with `x` and `y` properties of the coordinates */ table.getCellPosition = function (cell, tableGrid) { var rowIndex, index; @@ -23,4 +26,4 @@ table.getCellPosition = function (cell, tableGrid) { } } -}; \ No newline at end of file +}; diff --git a/lib/commons/table/get-headers.js b/lib/commons/table/get-headers.js index b9b19b5aff..15b641802b 100644 --- a/lib/commons/table/get-headers.js +++ b/lib/commons/table/get-headers.js @@ -1,9 +1,12 @@ -/*global table */ +/* global table */ /** * Get any associated table headers for a `HTMLTableCellElement` + * @method getHeaders + * @memberof axe.commons.table + * @instance * @param {HTMLTableCellElement} cell The cell of which to get headers - * @return {Array} Array of headers associated to the table cell + * @return {Array} Array of headers associated to the table cell */ table.getHeaders = function (cell) { @@ -24,4 +27,4 @@ table.getHeaders = function (cell) { .filter((cell) => table.isColumnHeader(cell)); return [].concat(rowHeaders, colHeaders).reverse(); -}; \ No newline at end of file +}; diff --git a/lib/commons/table/get-scope.js b/lib/commons/table/get-scope.js index 0d2ce03dff..5b6d7d8466 100644 --- a/lib/commons/table/get-scope.js +++ b/lib/commons/table/get-scope.js @@ -1,9 +1,12 @@ -/*global table, dom */ +/* global table, dom */ /** - * Determine if a `HTMLTableCellElement` is a column header - * @param {HTMLTableCellElement} node The table cell to test - * @return {Boolean} + * Determine if a `HTMLTableCellElement` is a column header, if so get the scope of the header + * @method getScope + * @memberof axe.commons.table + * @instance + * @param {HTMLTableCellElement} cell The table cell to test + * @return {Boolean|String} Returns `false` if not a column header, or the scope of the column header element */ table.getScope = function (cell) { var scope = cell.getAttribute('scope'); @@ -49,4 +52,4 @@ table.getScope = function (cell) { return 'row'; } return 'auto'; -}; \ No newline at end of file +}; diff --git a/lib/commons/table/index.js b/lib/commons/table/index.js index 35a35d764f..901a727159 100644 --- a/lib/commons/table/index.js +++ b/lib/commons/table/index.js @@ -1,2 +1,9 @@ -/*exported table */ +/* exported table */ + +/** + * Namespace for table-related utilities. + * @namespace table + * @memberof axe.commons + */ + var table = commons.table = {}; diff --git a/lib/commons/table/is-column-header.js b/lib/commons/table/is-column-header.js index 83386f1741..0e59f2e9ea 100644 --- a/lib/commons/table/is-column-header.js +++ b/lib/commons/table/is-column-header.js @@ -1,10 +1,13 @@ -/*global table */ +/* global table */ /** * Determine if a `HTMLTableCellElement` is a column header - * @param {HTMLTableCellElement} node The table cell to test + * @method isColumnHeader + * @memberof axe.commons.table + * @instance + * @param {HTMLTableCellElement} element The table cell to test * @return {Boolean} */ -table.isColumnHeader = function (node) { - return (['col', 'auto'].indexOf(table.getScope(node)) !== -1); -}; \ No newline at end of file +table.isColumnHeader = function (element) { + return (['col', 'auto'].indexOf(table.getScope(element)) !== -1); +}; diff --git a/lib/commons/table/is-data-cell.js b/lib/commons/table/is-data-cell.js index 1593825ee0..0a0b97e3f0 100644 --- a/lib/commons/table/is-data-cell.js +++ b/lib/commons/table/is-data-cell.js @@ -1,8 +1,11 @@ -/*global table */ +/* global table */ /** * Determine if a `HTMLTableCellElement` is a data cell - * @param {HTMLTableCellElement} node The table cell to test + * @method isDataCell + * @memberof axe.commons.table + * @instance + * @param {HTMLTableCellElement} node The table cell to test * @return {Boolean} */ table.isDataCell = function (cell) { @@ -11,4 +14,4 @@ table.isDataCell = function (cell) { return false; } return cell.nodeName.toUpperCase() === 'TD'; -}; \ No newline at end of file +}; diff --git a/lib/commons/table/is-data-table.js b/lib/commons/table/is-data-table.js index ade3c2dd4f..5130b3a8d1 100644 --- a/lib/commons/table/is-data-table.js +++ b/lib/commons/table/is-data-table.js @@ -1,15 +1,18 @@ -/*global table, dom */ -/*jshint maxstatements: 70, maxcomplexity: 40 */ +/* global table, dom */ +/* jshint maxstatements: 70, maxcomplexity: 40 */ /** * Determines whether a table is a data table - * @param {HTMLTableElement} node The table to test + * @method isDataTable + * @memberof axe.commons.table + * @instance + * @param {HTMLTableElement} node The table to test * @return {Boolean} * @see http://asurkov.blogspot.co.uk/2011/10/data-vs-layout-table.html */ table.isDataTable = function (node) { - var role = node.getAttribute('role'); + var role = (node.getAttribute('role') || '').toLowerCase(); // The element is not focusable and has role=presentation if ((role === 'presentation' || role === 'none') && !dom.isFocusable(node)) { @@ -70,7 +73,7 @@ table.isDataTable = function (node) { if (cell.getAttribute('scope') || cell.getAttribute('headers') || cell.getAttribute('abbr')) { return true; } - if (['columnheader', 'rowheader'].indexOf(cell.getAttribute('role')) !== -1) { + if (['columnheader', 'rowheader'].includes((cell.getAttribute('role') || '').toLowerCase())) { return true; } // abbr element as a single child element of table cell diff --git a/lib/commons/table/is-header.js b/lib/commons/table/is-header.js index 5edcb8df77..69d4890f6b 100644 --- a/lib/commons/table/is-header.js +++ b/lib/commons/table/is-header.js @@ -1,8 +1,11 @@ -/*global table, axe */ +/* global table, axe */ /** * Determine if a `HTMLTableCellElement` is a header - * @param {HTMLTableCellElement} node The table cell to test + * @method isHeader + * @memberof axe.commons.table + * @instance + * @param {HTMLTableCellElement} cell The table cell to test * @return {Boolean} */ table.isHeader = function (cell) { diff --git a/lib/commons/table/is-row-header.js b/lib/commons/table/is-row-header.js index 8516a547db..4a934f8885 100644 --- a/lib/commons/table/is-row-header.js +++ b/lib/commons/table/is-row-header.js @@ -1,10 +1,13 @@ -/*global table */ +/* global table */ /** * Determine if a `HTMLTableCellElement` is a row header - * @param {HTMLTableCellElement} node The table cell to test + * @method isRowHeader + * @memberof axe.commons.table + * @instance + * @param {HTMLTableCellElement} cell The table cell to test * @return {Boolean} */ -table.isRowHeader = function (node) { - return (['row', 'auto'].indexOf(table.getScope(node)) !== -1); -}; \ No newline at end of file +table.isRowHeader = function (cell) { + return (['row', 'auto'].includes(table.getScope(cell))); +}; diff --git a/lib/commons/table/to-grid.js b/lib/commons/table/to-grid.js index 565d203212..74a2566aeb 100644 --- a/lib/commons/table/to-grid.js +++ b/lib/commons/table/to-grid.js @@ -2,8 +2,11 @@ /** * Converts a table to an Array of arrays, normalized for row and column spans + * @method toGrid + * @memberof axe.commons.table + * @instance * @param {HTMLTableElement} node The table to convert - * @return {Array} Array of rows and cells + * @return {Array} Array of HTMLTableCellElements */ table.toGrid = function (node) { var table = []; @@ -32,4 +35,4 @@ table.toGrid = function (node) { }; // This was the old name -table.toArray = table.toGrid; \ No newline at end of file +table.toArray = table.toGrid; diff --git a/lib/commons/table/traverse.js b/lib/commons/table/traverse.js index 18d9d6b002..c971a82d33 100644 --- a/lib/commons/table/traverse.js +++ b/lib/commons/table/traverse.js @@ -23,12 +23,15 @@ }; /** - * Traverses a table in a given direction, passing it to the callback - * @param {object} dir Direction that will be added recursively {x: 1, y: 0}; - * @param {object} start x/y coordinate: {x: 0, y: 0}; - * @param {array} [table] A matrix of the table obtained using axe.commons.table.toArray (OPTIONAL) + * Traverses a table in a given direction, passing the cell to the callback + * @method traverse + * @memberof axe.commons.table + * @instance + * @param {Object|String} dir Direction that will be added recursively {x: 1, y: 0}, 'left'; + * @param {Object} startPos x/y coordinate: {x: 0, y: 0}; + * @param {Array} [tablegrid] A matrix of the table obtained using axe.commons.table.toArray (OPTIONAL) * @param {Function} callback Function to which each cell will be passed - * @return {nodeElemtn} If the callback returns true, the traversal will end and the cell will be returned + * @return {NodeElement} If the callback returns true, the traversal will end and the cell will be returned */ table.traverse = function (dir, startPos, tableGrid, callback) { if (Array.isArray(startPos)) { @@ -56,4 +59,4 @@ }, tableGrid, callback); }; -}(table)); \ No newline at end of file +}(table)); diff --git a/lib/commons/text/accessible-text.js b/lib/commons/text/accessible-text.js index ab7dcac2d8..f1e0f7fad2 100644 --- a/lib/commons/text/accessible-text.js +++ b/lib/commons/text/accessible-text.js @@ -1,5 +1,5 @@ -/*global text, dom, aria, axe */ -/*jshint maxstatements: 25, maxcomplexity: 19 */ +/* global text, dom, aria, axe */ +/* jshint maxstatements: 25, maxcomplexity: 19 */ var defaultButtonValues = { submit: 'Submit', @@ -16,6 +16,7 @@ var phrasingElements = ['A', 'EM', 'STRONG', 'SMALL', 'MARK', 'ABBR', 'DFN', 'I' /** * Find a non-ARIA label for an element * + * @private * @param {HTMLElement} element The HTMLElement * @return {HTMLElement} The label element, or null if none is found */ @@ -52,7 +53,7 @@ function shouldNeverCheckSubtree(element) { /** * Calculate value of a form element when treated as a value - * + * @private * @param {HTMLElement} element The HTMLElement * @return {string} The calculated value */ @@ -99,7 +100,7 @@ function checkDescendant(element, nodeName) { /** * Determine whether an element can be an embedded control - * + * @private * @param {HTMLElement} element The HTMLElement * @return {boolean} True if embedded control */ @@ -133,6 +134,9 @@ function nonEmptyText(t) { * http://www.w3.org/TR/html-aam-1.0/ * http://www.w3.org/TR/wai-aria/roles#textalternativecomputation * + * @method accessibleText + * @memberof axe.commons.text + * @instance * @param {HTMLElement} element The HTMLElement * @param {Boolean} inLabelledByContext True when in the context of resolving a labelledBy * @return {string} diff --git a/lib/commons/text/index.js b/lib/commons/text/index.js index 108d64bd8c..b8e4b503f2 100644 --- a/lib/commons/text/index.js +++ b/lib/commons/text/index.js @@ -1,2 +1,9 @@ -/*exported text */ -var text = commons.text = {}; \ No newline at end of file +/* exported text */ + +/** + * Namespace for text-related utilities. + * @namespace text + * @memberof axe.commons + */ + +var text = commons.text = {}; diff --git a/lib/commons/text/sanitize.js b/lib/commons/text/sanitize.js index 369cefd828..e6411b418b 100644 --- a/lib/commons/text/sanitize.js +++ b/lib/commons/text/sanitize.js @@ -1,4 +1,13 @@ -/*global text */ +/* global text */ + +/** + * Removes carriage returns, newline characters, tabs, non-breaking spaces, and trailing spaces from string + * @method sanitize + * @memberof axe.commons.text + * @instance + * @param {String} str String to be cleaned + * @return {String} Sanitized string + */ text.sanitize = function (str) { 'use strict'; return str diff --git a/lib/commons/text/visible.js b/lib/commons/text/visible.js index cc9ae0e427..17cf513236 100644 --- a/lib/commons/text/visible.js +++ b/lib/commons/text/visible.js @@ -1,5 +1,17 @@ -/*global text, dom */ +/* global text, dom */ +/** + * Returns the visible text of the node + * + * @method visible + * @memberof axe.commons.text + * @instance + * @param {Element} element + * @param {Boolean} screenReader When provided, will evaluate visiblility from the perspective of a screen reader + * @param {Boolean} noRecursing When False, the result will contain text from the element and it's children. + * When True, the result will only contain text from the element + * @return {String} + */ text.visible = function (element, screenReader, noRecursing) { 'use strict'; diff --git a/lib/commons/utils/index.js b/lib/commons/utils/index.js index 8c2d0e801c..fb9a079292 100644 --- a/lib/commons/utils/index.js +++ b/lib/commons/utils/index.js @@ -1,4 +1,10 @@ -/*exported utils */ -/*global axe */ -var utils = commons.utils = axe.utils; +/* exported utils */ +/* global axe */ + +/** + * Namespace for general utilities. + * @namespace utils + * @memberof axe.commons + */ +var utils = commons.utils = axe.utils; diff --git a/lib/commons/utils/to-array.js b/lib/commons/utils/to-array.js index eb5c170e5e..839b267706 100644 --- a/lib/commons/utils/to-array.js +++ b/lib/commons/utils/to-array.js @@ -1,5 +1,14 @@ -/*global axe */ +/* global axe */ + +/** + * Converts thing to an Array + * @method toArray + * @memberof axe.commons.utils + * @instance + * @param {NodeList|HTMLCollection|String} thing + * @return {Array} + */ axe.utils.toArray = function (thing) { 'use strict'; return Array.prototype.slice.call(thing); -}; \ No newline at end of file +}; diff --git a/lib/commons/utils/token-list.js b/lib/commons/utils/token-list.js index 8efccbd152..ae072027da 100644 --- a/lib/commons/utils/token-list.js +++ b/lib/commons/utils/token-list.js @@ -1,7 +1,14 @@ -/*global axe */ - +/* global axe */ +/** + * Converts space delimited token list to an Array + * @method tokenList + * @memberof axe.commons.utils + * @instance + * @param {String} str + * @return {Array} + */ axe.utils.tokenList = function (str) { 'use strict'; return str.trim().replace(/\s{2,}/g, ' ').split(' '); -}; \ No newline at end of file +}; diff --git a/lib/commons/utils/valid-langs.js b/lib/commons/utils/valid-langs.js index e406e13c76..68d140a6f9 100644 --- a/lib/commons/utils/valid-langs.js +++ b/lib/commons/utils/valid-langs.js @@ -1,5 +1,5 @@ -/*global axe */ -/*jshint -W109 */ +/* global axe */ +/* jshint -W109 */ var langs = [ "aa", "ab", @@ -8128,7 +8128,15 @@ var langs = [ "zza", "zzj" ]; + +/** + * Returns array of valid language codes + * @method validLangs + * @memberof axe.commons.utils + * @instance + * @return {Array} Valid language codes + */ axe.utils.validLangs = function () { 'use strict'; return langs; -}; \ No newline at end of file +}; diff --git a/lib/core/base/audit.js b/lib/core/base/audit.js index 055f78f12c..1ee559f205 100644 --- a/lib/core/base/audit.js +++ b/lib/core/base/audit.js @@ -297,7 +297,7 @@ function getHelpUrl ({brand, application}, ruleId, version) { } Audit.prototype._constructHelpUrls = function (previous = null) { - var version = (axe.version.match(/^[1-9][0-9]*\.[1-9][0-9]*/) || ['x.y'])[0]; + var version = (axe.version.match(/^[1-9][0-9]*\.[0-9]+/) || ['x.y'])[0]; this.rules.forEach(rule => { if (!this.data.rules[rule.id]) { this.data.rules[rule.id] = {}; diff --git a/lib/core/public/plugins.js b/lib/core/public/plugins.js index 65ba6253c1..ba71f9e40a 100644 --- a/lib/core/public/plugins.js +++ b/lib/core/public/plugins.js @@ -43,4 +43,4 @@ Plugin.prototype.add = function (impl) { axe.registerPlugin = function (plugin) { 'use strict'; axe.plugins[plugin.id] = new Plugin(plugin); -}; \ No newline at end of file +}; diff --git a/lib/core/utils/contains.js b/lib/core/utils/contains.js index 2ba198d5e2..bbedbe4bac 100644 --- a/lib/core/utils/contains.js +++ b/lib/core/utils/contains.js @@ -1,6 +1,9 @@ /** * Wrapper for Node#contains; PhantomJS does not support Node#contains and erroneously reports that it does + * @method contains + * @memberof axe.utils + * @instance * @param {HTMLElement} node The candidate container node * @param {HTMLElement} otherNode The node to test is contained by `node` * @return {Boolean} Whether `node` contains `otherNode` diff --git a/lib/core/utils/find-by.js b/lib/core/utils/find-by.js index c9b8ebf2ab..a649d0ec8e 100644 --- a/lib/core/utils/find-by.js +++ b/lib/core/utils/find-by.js @@ -1,6 +1,9 @@ /** * Iterates an array of objects looking for a property with a specific value + * @method findBy + * @memberof axe.utils + * @instance * @param {Array} array The array of objects to iterate * @param {String} key The property name to test against * @param {Mixed} value The value to find @@ -10,4 +13,4 @@ axe.utils.findBy = function (array, key, value) { if (Array.isArray(array)) { return array.find( obj => typeof obj === 'object' && obj[key] === value ); } -}; \ No newline at end of file +}; diff --git a/lib/core/utils/index.js b/lib/core/utils/index.js index c85df45266..aee1e38fe6 100644 --- a/lib/core/utils/index.js +++ b/lib/core/utils/index.js @@ -1,2 +1,9 @@ -/*exported utils */ +/* exported utils */ + +/** + * Namespace for utility helper methods. + * @namespace utils + * @memberof axe + */ + var utils = axe.utils = {}; diff --git a/lib/core/utils/is-hidden.js b/lib/core/utils/is-hidden.js index 13e86f437c..464132130b 100644 --- a/lib/core/utils/is-hidden.js +++ b/lib/core/utils/is-hidden.js @@ -2,8 +2,11 @@ /** * Determine whether an element is visible - * + * @method isHidden + * @memberof axe.utils + * @instance * @param {HTMLElement} el The HTMLElement + * @param {Boolean} recursed * @return {Boolean} The element's visibilty status */ axe.utils.isHidden = function isHidden(el, recursed) { diff --git a/lib/core/utils/select.js b/lib/core/utils/select.js index 54156d4a63..bbc6403c26 100644 --- a/lib/core/utils/select.js +++ b/lib/core/utils/select.js @@ -56,7 +56,7 @@ function pushNode(result, nodes, context) { } /** - * Selects elements which match `select` that are included and excluded via the `Context` object + * Selects elements which match `selector` that are included and excluded via the `Context` object * @param {String} selector CSS selector of the HTMLElements to select * @param {Context} context The "resolved" context object, @see Context * @return {Array} Matching nodes sorted by DOM order diff --git a/sri-history.json b/sri-history.json index f0ce329565..61fefd3ab8 100644 --- a/sri-history.json +++ b/sri-history.json @@ -67,4 +67,4 @@ "axe.js": "sha256-NYUXSdma9KjPfzmpt7jw/hlbmeAha8K3zEA2UOW+eWw=", "axe.min.js": "sha256-7MV3BvKtgHeecwFjYOBYJbmOhvh2wdTGU7odxgpcrG0=" } -} \ No newline at end of file +} diff --git a/test/.jshintrc b/test/.jshintrc index dce6c75bef..d8372a2fbc 100644 --- a/test/.jshintrc +++ b/test/.jshintrc @@ -3,6 +3,7 @@ "globals": { "describe": true, "it": true, + "xit": true, "before": true, "beforeEach": true, "after": true, diff --git a/test/checks/.jshintrc b/test/checks/.jshintrc index 83f8eee74e..01fad568db 100644 --- a/test/checks/.jshintrc +++ b/test/checks/.jshintrc @@ -3,6 +3,7 @@ "globals": { "describe": true, "it": true, + "xit": true, "before": true, "beforeEach": true, "after": true, diff --git a/test/commons/.jshintrc b/test/commons/.jshintrc index 1c4d868423..7cca133389 100644 --- a/test/commons/.jshintrc +++ b/test/commons/.jshintrc @@ -3,6 +3,7 @@ "globals": { "describe": true, "it": true, + "xit": true, "before": true, "beforeEach": true, "after": true, diff --git a/test/commons/color/contrast.js b/test/commons/color/contrast.js index c9c4ebf19b..ff79614c3f 100644 --- a/test/commons/color/contrast.js +++ b/test/commons/color/contrast.js @@ -114,12 +114,19 @@ describe('color.Color', function () { assert.isTrue(axe.commons.color.hasValidContrastRatio(black, white, 8, false).isValid); assert.isTrue(axe.commons.color.hasValidContrastRatio(black, white, 8, false).contrastRatio > 4.5); + assert.isTrue(axe.commons.color.hasValidContrastRatio(black, white, 8, false).expectedContrastRatio === 4.5); + assert.isTrue(axe.commons.color.hasValidContrastRatio(white, gray, 24, false).isValid); assert.isTrue(axe.commons.color.hasValidContrastRatio(white, gray, 24, false).contrastRatio > 3); + assert.isTrue(axe.commons.color.hasValidContrastRatio(white, gray, 24, false).expectedContrastRatio === 3); + assert.isTrue(axe.commons.color.hasValidContrastRatio(white, gray, 20, true).isValid); assert.isTrue(axe.commons.color.hasValidContrastRatio(white, gray, 20, true).contrastRatio > 3); + assert.isTrue(axe.commons.color.hasValidContrastRatio(white, gray, 20, true).expectedContrastRatio === 3); + assert.isFalse(axe.commons.color.hasValidContrastRatio(white, gray, 8, false).isValid); assert.isTrue(axe.commons.color.hasValidContrastRatio(white, gray, 8, false).contrastRatio < 4.5); + assert.isTrue(axe.commons.color.hasValidContrastRatio(white, gray, 8, false).expectedContrastRatio === 4.5); }); it('should count 1-1 ratios as visually hidden', function () { @@ -127,5 +134,6 @@ describe('color.Color', function () { assert.isFalse(axe.commons.color.hasValidContrastRatio(black, black, 16, true).isValid); assert.isTrue(axe.commons.color.hasValidContrastRatio(black, black, 16, true).contrastRatio === 1); + assert.isTrue(axe.commons.color.hasValidContrastRatio(black, black, 16, true).expectedContrastRatio === 4.5); }); }); diff --git a/test/core/base/audit.js b/test/core/base/audit.js index cc52a98130..50758bbca9 100644 --- a/test/core/base/audit.js +++ b/test/core/base/audit.js @@ -181,6 +181,22 @@ describe('Audit', function () { assert.equal(audit.data.rules.target.helpUrl, 'https://dequeuniversity.com/rules/axe/x.y/target?application=axeAPI'); }); + it('matches major release versions', function () { + var tempVersion = axe.version; + var audit = new Audit(); + audit.addRule({ + id: 'target', + matches: 'function () {return "hello";}', + selector: 'bob' + }); + + axe.version = '1.0.0'; + audit._constructHelpUrls(); + + axe.version = tempVersion; + assert.equal(audit.data.rules.target.helpUrl, + 'https://dequeuniversity.com/rules/axe/1.0/target?application=axeAPI'); + }); }); describe('Audit#setBranding', function () { diff --git a/test/core/utils/send-command-to-frame.js b/test/core/utils/send-command-to-frame.js index e606bba534..b2f34e1d42 100644 --- a/test/core/utils/send-command-to-frame.js +++ b/test/core/utils/send-command-to-frame.js @@ -60,16 +60,18 @@ describe('axe.utils.sendCommandToFrame', function () { var called = 0; var frame = document.createElement('iframe'); frame.addEventListener('load', function () { - axe.utils.sendCommandToFrame(frame, { - number: number, - keepalive: true - }, function () { - called += 1; - if (called === number) { - assert.isTrue(true); - done(); - } - }, assertNotCalled); + setTimeout(function () { + axe.utils.sendCommandToFrame(frame, { + number: number, + keepalive: true + }, function () { + called += 1; + if (called === number) { + assert.isTrue(true); + done(); + } + }, assertNotCalled); + }, 500); }); frame.id = 'level0'; diff --git a/test/integration/rules/aria-allowed-attr/failures.html b/test/integration/rules/aria-allowed-attr/failures.html index 6fcc27e848..d6760c0c4a 100644 --- a/test/integration/rules/aria-allowed-attr/failures.html +++ b/test/integration/rules/aria-allowed-attr/failures.html @@ -1,3 +1,5 @@
fail
+
fail
+
fail
diff --git a/test/integration/rules/aria-allowed-attr/failures.json b/test/integration/rules/aria-allowed-attr/failures.json index 311e1ea5ab..06831d9b49 100644 --- a/test/integration/rules/aria-allowed-attr/failures.json +++ b/test/integration/rules/aria-allowed-attr/failures.json @@ -2,6 +2,6 @@ "description": "aria-allowed-attr failing tests", "rule": "aria-allowed-attr", "violations": [ - ["#fail1"], ["#fail2"] + ["#fail1"], ["#fail2"], ["#fail3"], ["#fail4"] ] -} \ No newline at end of file +} diff --git a/test/integration/rules/aria-allowed-attr/passes.html b/test/integration/rules/aria-allowed-attr/passes.html index 079840546c..90f5387c48 100644 --- a/test/integration/rules/aria-allowed-attr/passes.html +++ b/test/integration/rules/aria-allowed-attr/passes.html @@ -35,7 +35,7 @@
ok
-
ok
+
ok
ok
@@ -85,7 +85,7 @@
ok
-
ok
+
ok
ok
diff --git a/test/integration/rules/aria-allowed-attr/passes.json b/test/integration/rules/aria-allowed-attr/passes.json index d6e9ea5ece..f131d231e4 100644 --- a/test/integration/rules/aria-allowed-attr/passes.json +++ b/test/integration/rules/aria-allowed-attr/passes.json @@ -11,6 +11,6 @@ ["#pass43"], ["#pass44"], ["#pass45"], ["#pass46"], ["#pass47"], ["#pass48"], ["#pass49"], ["#pass50"], ["#pass51"], ["#pass52"], ["#pass53"], ["#pass54"], ["#pass55"], ["#pass56"], ["#pass57"], ["#pass58"], ["#pass59"], ["#pass60"], ["#pass61"], ["#pass62"], ["#pass63"], - ["#pass64"], ["#pass65"], ["#pass66"], ["#pass67"], ["#pass68"] + ["#pass64"], ["#pass65"], ["#pass66"], ["#pass67"], ["#pass68"] ] } diff --git a/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.html b/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.html index 85f62e5d72..26c132972a 100644 --- a/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.html +++ b/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.html @@ -246,6 +246,7 @@

Possible False Positives

hi
hi
hi
+
hi
hi
hi
From 419f9096249f6cc7e573f4cba27075fbaa1f8939 Mon Sep 17 00:00:00 2001 From: Marcy Sutton Date: Fri, 8 Dec 2017 17:59:50 -0800 Subject: [PATCH 2/2] docs: rename aria._lut, add to developer guide --- doc/developer-guide.md | 15 +++++++++ lib/commons/aria/attributes.js | 12 +++---- lib/commons/aria/index.js | 8 ++--- lib/commons/aria/roles.js | 26 +++++++-------- test/commons/aria/attributes.js | 58 ++++++++++++++++----------------- test/commons/aria/roles.js | 58 ++++++++++++++++----------------- 6 files changed, 96 insertions(+), 81 deletions(-) diff --git a/doc/developer-guide.md b/doc/developer-guide.md index c0ded55c1a..079ec1f692 100644 --- a/doc/developer-guide.md +++ b/doc/developer-guide.md @@ -162,6 +162,21 @@ Common functions are an internal library used by the rules and checks. If you h Core Utilities are an internal library that provides aXe with functionality used throughout its core processes. Most notably among these are the queue function and the DqElement constructor. +#### ARIA Lookup Tables + +axe.commons.aria provides a namespace for ARIA-related utilities, including a lookupTable for attributes and roles. + +* `axe.commons.aria.lookupTable.attributes` +* `axe.commons.aria.lookupTable.globalAttributes` +* `axe.commons.aria.lookupTable.role` + +#### Common Utility Functions + +In addition to the ARIA lookupTable, there are also utility functions on the axe.commons.aria and axe.commons.dom namespaces: + +* `axe.commons.aria.implicitRole` - Get the implicit role for a given node +* `axe.commons.aria.label` - Gets the accessible ARIA label text of a given element +* `axe.commons.dom.isVisible` - Determine whether an element is visible #### Queue Function diff --git a/lib/commons/aria/attributes.js b/lib/commons/aria/attributes.js index acbc00f10a..f846ca3495 100644 --- a/lib/commons/aria/attributes.js +++ b/lib/commons/aria/attributes.js @@ -1,4 +1,4 @@ -/*global aria, axe, lookupTables */ +/*global aria, axe, lookupTable */ /** * Get required attributes for a given role @@ -10,7 +10,7 @@ */ aria.requiredAttr = function (role) { 'use strict'; - var roles = lookupTables.role[role], + var roles = lookupTable.role[role], attr = roles && roles.attributes && roles.attributes.required; return attr || []; }; @@ -25,11 +25,11 @@ aria.requiredAttr = function (role) { */ aria.allowedAttr = function (role) { 'use strict'; - var roles = lookupTables.role[role], + var roles = lookupTable.role[role], attr = (roles && roles.attributes && roles.attributes.allowed) || [], requiredAttr = (roles && roles.attributes && roles.attributes.required) || []; - return attr.concat(lookupTables.globalAttributes).concat(requiredAttr); + return attr.concat(lookupTable.globalAttributes).concat(requiredAttr); }; /** @@ -42,7 +42,7 @@ aria.allowedAttr = function (role) { */ aria.validateAttr = function (att) { 'use strict'; - return !!lookupTables.attributes[att]; + return !!lookupTable.attributes[att]; }; /** @@ -60,7 +60,7 @@ aria.validateAttrValue = function (node, attr) { var matches, list, doc = document, value = node.getAttribute(attr), - attrInfo = lookupTables.attributes[attr]; + attrInfo = lookupTable.attributes[attr]; if (!attrInfo) { return true; diff --git a/lib/commons/aria/index.js b/lib/commons/aria/index.js index 18b4168834..faf555c02e 100644 --- a/lib/commons/aria/index.js +++ b/lib/commons/aria/index.js @@ -5,9 +5,9 @@ */ var aria = commons.aria = {}, - lookupTables = aria._lut = {}; + lookupTable = aria.lookupTable = {}; -lookupTables.attributes = { +lookupTable.attributes = { 'aria-activedescendant': { type: 'idref' }, @@ -171,14 +171,14 @@ lookupTables.attributes = { } }; -lookupTables.globalAttributes = [ +lookupTable.globalAttributes = [ 'aria-atomic', 'aria-busy', 'aria-controls', 'aria-current', 'aria-describedby', 'aria-disabled', 'aria-dropeffect', 'aria-flowto', 'aria-grabbed', 'aria-haspopup', 'aria-hidden', 'aria-invalid', 'aria-keyshortcuts', 'aria-label', 'aria-labelledby', 'aria-live', 'aria-owns', 'aria-relevant' ]; -lookupTables.role = { +lookupTable.role = { 'alert': { type: 'widget', attributes: { diff --git a/lib/commons/aria/roles.js b/lib/commons/aria/roles.js index 56bd52b4b8..a904df3bf7 100644 --- a/lib/commons/aria/roles.js +++ b/lib/commons/aria/roles.js @@ -1,4 +1,4 @@ -/* global aria, lookupTables, axe */ +/* global aria, lookupTable, axe */ /** * Check if a given role is valid @@ -10,7 +10,7 @@ */ aria.isValidRole = function (role) { 'use strict'; - if (lookupTables.role[role]) { + if (lookupTable.role[role]) { return true; } @@ -25,9 +25,9 @@ aria.isValidRole = function (role) { * @return {Array} Array of roles that match the type */ aria.getRolesWithNameFromContents = function () { - return Object.keys(lookupTables.role).filter(function (r) { - return lookupTables.role[r].nameFrom && - lookupTables.role[r].nameFrom.indexOf('contents') !== -1; + return Object.keys(lookupTable.role).filter(function (r) { + return lookupTable.role[r].nameFrom && + lookupTable.role[r].nameFrom.indexOf('contents') !== -1; }); }; @@ -40,8 +40,8 @@ aria.getRolesWithNameFromContents = function () { * @return {Array} Array of roles that match the type */ aria.getRolesByType = function (roleType) { - return Object.keys(lookupTables.role).filter(function (r) { - return lookupTables.role[r].type === roleType; + return Object.keys(lookupTable.role).filter(function (r) { + return lookupTable.role[r].type === roleType; }); }; @@ -54,7 +54,7 @@ aria.getRolesByType = function (roleType) { * @return {Mixed} String if a matching role and its type are found, otherwise `null` */ aria.getRoleType = function (role) { - var r = lookupTables.role[role]; + var r = lookupTable.role[role]; return (r && r.type) || null; }; @@ -70,7 +70,7 @@ aria.getRoleType = function (role) { aria.requiredOwned = function (role) { 'use strict'; var owned = null, - roles = lookupTables.role[role]; + roles = lookupTable.role[role]; if (roles) { owned = axe.utils.clone(roles.owned); @@ -89,7 +89,7 @@ aria.requiredOwned = function (role) { aria.requiredContext = function (role) { 'use strict'; var context = null, - roles = lookupTables.role[role]; + roles = lookupTable.role[role]; if (roles) { context = axe.utils.clone(roles.context); @@ -109,7 +109,7 @@ aria.implicitNodes = function (role) { 'use strict'; var implicit = null, - roles = lookupTables.role[role]; + roles = lookupTable.role[role]; if (roles && roles.implicit) { implicit = axe.utils.clone(roles.implicit); @@ -172,8 +172,8 @@ aria.implicitRole = function (node) { /* * Create a list of { name / implicit } role mappings to filter on */ - var roles = Object.keys(lookupTables.role).map( function (role) { - var lookup = lookupTables.role[role]; + var roles = Object.keys(lookupTable.role).map( function (role) { + var lookup = lookupTable.role[role]; return { name: role, implicit: lookup && lookup.implicit }; }); diff --git a/test/commons/aria/attributes.js b/test/commons/aria/attributes.js index 88f947b4b8..64293f5bb3 100644 --- a/test/commons/aria/attributes.js +++ b/test/commons/aria/attributes.js @@ -4,15 +4,15 @@ describe('aria.requiredAttr', function () { var orig; beforeEach(function () { - orig = axe.commons.aria._lut.role; + orig = axe.commons.aria.lookupTable.role; }); afterEach(function () { - axe.commons.aria._lut.role = orig; + axe.commons.aria.lookupTable.role = orig; }); it('should returned the attributes property for the proper role', function () { - axe.commons.aria._lut.role = { + axe.commons.aria.lookupTable.role = { 'cats': { attributes: { required: 'yes' @@ -24,7 +24,7 @@ describe('aria.requiredAttr', function () { }); it('should return an empty array if there are no required attributes', function () { - axe.commons.aria._lut.role = {}; + axe.commons.aria.lookupTable.role = {}; var result = axe.commons.aria.requiredAttr('cats'); assert.deepEqual(result, []); @@ -37,16 +37,16 @@ describe('aria.allowedAttr', function () { var orig; beforeEach(function () { - orig = axe.commons.aria._lut.role; + orig = axe.commons.aria.lookupTable.role; }); afterEach(function () { - axe.commons.aria._lut.role = orig; + axe.commons.aria.lookupTable.role = orig; }); it('should returned the attributes property for the proper role', function () { - var orig = axe.commons.aria._lut.globalAttributes = ['world']; - axe.commons.aria._lut.role = { + var orig = axe.commons.aria.lookupTable.globalAttributes = ['world']; + axe.commons.aria.lookupTable.role = { 'cats': { attributes: { allowed: ['hello'] @@ -55,12 +55,12 @@ describe('aria.allowedAttr', function () { }; assert.deepEqual(axe.commons.aria.allowedAttr('cats'), ['hello', 'world']); - axe.commons.aria._lut.globalAttributes = orig; + axe.commons.aria.lookupTable.globalAttributes = orig; }); it('should also check required attributes', function () { - var orig = axe.commons.aria._lut.globalAttributes = ['world']; - axe.commons.aria._lut.role = { + var orig = axe.commons.aria.lookupTable.globalAttributes = ['world']; + axe.commons.aria.lookupTable.role = { 'cats': { attributes: { required: ['hello'], @@ -70,18 +70,18 @@ describe('aria.allowedAttr', function () { }; assert.deepEqual(axe.commons.aria.allowedAttr('cats'), ['ok', 'world', 'hello']); - axe.commons.aria._lut.globalAttributes = orig; + axe.commons.aria.lookupTable.globalAttributes = orig; }); it('should return an array with globally allowed attributes', function () { var result, - orig = axe.commons.aria._lut.globalAttributes = ['world']; + orig = axe.commons.aria.lookupTable.globalAttributes = ['world']; - axe.commons.aria._lut.role = {}; + axe.commons.aria.lookupTable.role = {}; result = axe.commons.aria.allowedAttr('cats'); assert.deepEqual(result, ['world']); - axe.commons.aria._lut.globalAttributes = orig; + axe.commons.aria.lookupTable.globalAttributes = orig; }); }); @@ -91,15 +91,15 @@ describe('aria.validateAttr', function () { var orig; beforeEach(function () { - orig = axe.commons.aria._lut.attributes; + orig = axe.commons.aria.lookupTable.attributes; }); afterEach(function () { - axe.commons.aria._lut.attributes = orig; + axe.commons.aria.lookupTable.attributes = orig; }); it('should return true if attribute is found in lut', function () { - axe.commons.aria._lut.attributes = { + axe.commons.aria.lookupTable.attributes = { 'cats': {} }; @@ -107,7 +107,7 @@ describe('aria.validateAttr', function () { }); it('should return false if attribute is found in lut', function () { - axe.commons.aria._lut.attributes = {}; + axe.commons.aria.lookupTable.attributes = {}; assert.isFalse(axe.commons.aria.validateAttr('cats')); }); @@ -116,11 +116,11 @@ describe('aria.validateAttr', function () { describe('aria.validateAttrValue', function () { 'use strict'; - var orig = axe.commons.aria._lut.attributes, + var orig = axe.commons.aria.lookupTable.attributes, fixture = document.getElementById('fixture'); afterEach(function () { - axe.commons.aria._lut.attributes = orig; + axe.commons.aria.lookupTable.attributes = orig; fixture.innerHTML = ''; }); @@ -135,7 +135,7 @@ describe('aria.validateAttrValue', function () { describe('enumerated values', function () { it('should validate against enumerated .values if present', function () { - axe.commons.aria._lut.attributes = { + axe.commons.aria.lookupTable.attributes = { cats: { type: 'nmtoken', values: ['valid'] @@ -152,7 +152,7 @@ describe('aria.validateAttrValue', function () { }); it('should be case-insensitive for enumerated values', function () { - axe.commons.aria._lut.attributes = { + axe.commons.aria.lookupTable.attributes = { cats: { type: 'nmtoken', values: ['valid'] @@ -165,7 +165,7 @@ describe('aria.validateAttrValue', function () { }); it('should reject empty strings', function () { - axe.commons.aria._lut.attributes = { + axe.commons.aria.lookupTable.attributes = { cats: { type: 'nmtoken', values: ['valid'] @@ -179,7 +179,7 @@ describe('aria.validateAttrValue', function () { }); describe('idref', function () { it('should validate the referenced node exists', function () { - axe.commons.aria._lut.attributes = { + axe.commons.aria.lookupTable.attributes = { cats: { type: 'idref' } @@ -198,7 +198,7 @@ describe('aria.validateAttrValue', function () { var node = document.createElement('div'); beforeEach(function () { - axe.commons.aria._lut.attributes = { + axe.commons.aria.lookupTable.attributes = { cats: { type: 'idrefs' } @@ -236,7 +236,7 @@ describe('aria.validateAttrValue', function () { describe('string', function () { it('should always return true', function () { - axe.commons.aria._lut.attributes = { + axe.commons.aria.lookupTable.attributes = { cats: { type: 'string' } @@ -250,7 +250,7 @@ describe('aria.validateAttrValue', function () { describe('decimal', function () { var node = document.createElement('div'); beforeEach(function () { - axe.commons.aria._lut.attributes = { + axe.commons.aria.lookupTable.attributes = { cats: { type: 'decimal' } @@ -351,7 +351,7 @@ describe('aria.validateAttrValue', function () { describe('int', function () { var node = document.createElement('div'); beforeEach(function () { - axe.commons.aria._lut.attributes = { + axe.commons.aria.lookupTable.attributes = { cats: { type: 'int' } diff --git a/test/commons/aria/roles.js b/test/commons/aria/roles.js index 25e433367a..4b3f686dfc 100644 --- a/test/commons/aria/roles.js +++ b/test/commons/aria/roles.js @@ -3,12 +3,12 @@ describe('aria.isValidRole', function () { 'use strict'; it('should return true if role is found in the lookup table', function () { - var orig = axe.commons.aria._lut.role; - axe.commons.aria._lut.role = { + var orig = axe.commons.aria.lookupTable.role; + axe.commons.aria.lookupTable.role = { 'cats': true }; assert.isTrue(axe.commons.aria.isValidRole('cats')); - axe.commons.aria._lut.role = orig; + axe.commons.aria.lookupTable.role = orig; }); @@ -22,8 +22,8 @@ describe('aria.getRolesWithNameFromContents', function () { 'use strict'; it('should return array if nameFrom contents is found in the lookup table', function () { - var orig = axe.commons.aria._lut.role; - axe.commons.aria._lut.role = { + var orig = axe.commons.aria.lookupTable.role; + axe.commons.aria.lookupTable.role = { 'dogs': { type: 'things', nameFrom: ['author', 'contents'] @@ -34,7 +34,7 @@ describe('aria.getRolesWithNameFromContents', function () { } }; assert.deepEqual(axe.commons.aria.getRolesWithNameFromContents(), ['dogs']); - axe.commons.aria._lut.role = orig; + axe.commons.aria.lookupTable.role = orig; }); }); @@ -43,8 +43,8 @@ describe('aria.getRolesByType', function () { 'use strict'; it('should return array if roletype is found in the lookup table', function () { - var orig = axe.commons.aria._lut.role; - axe.commons.aria._lut.role = { + var orig = axe.commons.aria.lookupTable.role; + axe.commons.aria.lookupTable.role = { 'dogs': { type: 'things' }, @@ -53,7 +53,7 @@ describe('aria.getRolesByType', function () { } }; assert.deepEqual(axe.commons.aria.getRolesByType('stuff'), ['cats']); - axe.commons.aria._lut.role = orig; + axe.commons.aria.lookupTable.role = orig; }); @@ -66,14 +66,14 @@ describe('aria.getRoleType', function () { 'use strict'; it('should return true if role is found in the lookup table', function () { - var orig = axe.commons.aria._lut.role; - axe.commons.aria._lut.role = { + var orig = axe.commons.aria.lookupTable.role; + axe.commons.aria.lookupTable.role = { 'cats': { type: 'stuff' } }; assert.equal(axe.commons.aria.getRoleType('cats'), 'stuff'); - axe.commons.aria._lut.role = orig; + axe.commons.aria.lookupTable.role = orig; }); @@ -87,15 +87,15 @@ describe('aria.requiredOwned', function () { var orig; beforeEach(function () { - orig = axe.commons.aria._lut.role; + orig = axe.commons.aria.lookupTable.role; }); afterEach(function () { - axe.commons.aria._lut.role = orig; + axe.commons.aria.lookupTable.role = orig; }); it('should returned the owned property for the proper role', function () { - axe.commons.aria._lut.role = { + axe.commons.aria.lookupTable.role = { 'cats': { owned: 'yes' } @@ -105,7 +105,7 @@ describe('aria.requiredOwned', function () { }); it('should return null if there are no required owned nodes', function () { - axe.commons.aria._lut.role = {}; + axe.commons.aria.lookupTable.role = {}; var result = axe.commons.aria.requiredOwned('cats'); assert.isNull(result); @@ -118,15 +118,15 @@ describe('aria.requiredContext', function () { var orig; beforeEach(function () { - orig = axe.commons.aria._lut.role; + orig = axe.commons.aria.lookupTable.role; }); afterEach(function () { - axe.commons.aria._lut.role = orig; + axe.commons.aria.lookupTable.role = orig; }); it('should returned the context property for the proper role', function () { - axe.commons.aria._lut.role = { + axe.commons.aria.lookupTable.role = { 'cats': { context: 'yes' } @@ -136,7 +136,7 @@ describe('aria.requiredContext', function () { }); it('should return null if there are no required context nodes', function () { - axe.commons.aria._lut.role = {}; + axe.commons.aria.lookupTable.role = {}; var result = axe.commons.aria.requiredContext('cats'); assert.isNull(result); @@ -149,15 +149,15 @@ describe('aria.implicitNodes', function () { var orig; beforeEach(function () { - orig = axe.commons.aria._lut.role; + orig = axe.commons.aria.lookupTable.role; }); afterEach(function () { - axe.commons.aria._lut.role = orig; + axe.commons.aria.lookupTable.role = orig; }); it('should return the implicit property for the proper role', function () { - axe.commons.aria._lut.role = { + axe.commons.aria.lookupTable.role = { 'cats': { implicit: 'yes' } @@ -167,7 +167,7 @@ describe('aria.implicitNodes', function () { }); it('should return null if there are no implicit roles', function () { - axe.commons.aria._lut.role = {}; + axe.commons.aria.lookupTable.role = {}; var result = axe.commons.aria.implicitNodes('cats'); assert.isNull(result); @@ -181,12 +181,12 @@ describe('aria.implicitRole', function () { var fixture = document.getElementById('fixture'); var orig; beforeEach(function () { - orig = axe.commons.aria._lut.role; + orig = axe.commons.aria.lookupTable.role; }); afterEach(function () { fixture.innerHTML = ''; - axe.commons.aria._lut.role = orig; + axe.commons.aria.lookupTable.role = orig; }); it('should find the first optimal matching role', function () { @@ -196,7 +196,7 @@ describe('aria.implicitRole', function () { node.id = 'cats'; fixture.appendChild(node); - axe.commons.aria._lut.role = { + axe.commons.aria.lookupTable.role = { 'cats': { implicit: ['div[id="cats"]'] }, @@ -221,7 +221,7 @@ describe('aria.implicitRole', function () { node.id = 'cats'; fixture.appendChild(node); - axe.commons.aria._lut.role = { + axe.commons.aria.lookupTable.role = { 'cats': { implicit: ['div[id="cats"]'] }, @@ -248,7 +248,7 @@ describe('aria.implicitRole', function () { node.id = 'cats'; fixture.appendChild(node); - axe.commons.aria._lut.role = {}; + axe.commons.aria.lookupTable.role = {}; var result = axe.commons.aria.implicitRole(node); assert.isNull(result);