diff --git a/.circleci/config.yml b/.circleci/config.yml index bcb9f32dd0..f7b1061e29 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,11 +11,6 @@ restore_dependency_cache: &restore_dependency_cache - v{{ .Environment.CACHE_VERSION }}-npm-cache-{{ checksum "package.json" }} - v{{ .Environment.CACHE_VERSION }}-npm-cache- -restore_phantomjs_cache: &restore_phantomjs_cache - restore_cache: - keys: - - v{{ .Environment.CACHE_VERSION }}-phantomjs-2.1.1 - set_npm_auth: &set_npm_auth run: npm config set "//registry.npmjs.org/:_authToken" $NPM_AUTH @@ -47,14 +42,6 @@ jobs: steps: - checkout - <<: *restore_dependency_cache - - <<: *restore_phantomjs_cache - - run: - name: PhantomJs - Install or Restore from Cache - command: 'sh .circleci/install-phantomjs.sh' - - save_cache: - key: v{{ .Environment.CACHE_VERSION }}-phantomjs-2.1.1 - paths: - - phantomjs - run: npm run test # Run examples under `doc/examples` @@ -74,6 +61,14 @@ jobs: - run: npm run build - run: npm run test:locales + # Test api docs can be built + build_api_docs: + <<: *defaults + steps: + - checkout + - <<: *restore_dependency_cache + - run: npm run api-docs + # Test newest axe-core version rule help docs are active (only on # master prs) test_rule_help_version: @@ -138,6 +133,9 @@ workflows: - test_locales: requires: - test + - build_api_docs: + requires: + - test - test_rule_help_version: requires: - test @@ -148,6 +146,7 @@ workflows: - test - test_examples - test_locales + - build_api_docs - test_rule_help_version filters: branches: @@ -160,6 +159,7 @@ workflows: - test - test_examples - test_locales + - build_api_docs filters: branches: only: develop @@ -170,6 +170,7 @@ workflows: - test - test_examples - test_locales + - build_api_docs - test_rule_help_version - hold filters: diff --git a/.circleci/install-phantomjs.sh b/.circleci/install-phantomjs.sh deleted file mode 100644 index 7e2a6ab6db..0000000000 --- a/.circleci/install-phantomjs.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -if [ ! -e phantomjs ]; then - # phantomjs cache does not exist, fetch phantomjs - sudo curl --output phantomjs https://s3.amazonaws.com/circle-downloads/phantomjs-2.1.1 -fi - -# move phantomjs to bin -sudo cp -r phantomjs /usr/local/bin/phantomjs - -# update permissions -sudo chmod ugo+x /usr/local/bin/phantomjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 623a2cc2b0..adce62853e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [3.4.1](https://github.com/dequelabs/axe-core/compare/v3.4.0...v3.4.1) (2019-12-11) + +### Bug Fixes + +- ignores axe.ping responses that do not contain axe=true ([7d8aa42](https://github.com/dequelabs/axe-core/commit/7d8aa42)) +- input[role=button][value='correct'] should pass ([#1897](https://github.com/dequelabs/axe-core/issues/1897)) ([3aba02c](https://github.com/dequelabs/axe-core/commit/3aba02c)) +- remove heading from list of widget roles ([#1882](https://github.com/dequelabs/axe-core/issues/1882)) ([beb458f](https://github.com/dequelabs/axe-core/commit/beb458f)) +- **listitem:** clarify that li elements must be contained in a list or role=list ([#1894](https://github.com/dequelabs/axe-core/issues/1894)) ([8fa0964](https://github.com/dequelabs/axe-core/commit/8fa0964)) +- removes flaky test in axe Pro api check ([f2b3b54](https://github.com/dequelabs/axe-core/commit/f2b3b54)) +- **aria-allowed-role:** allow role combobox on input tel, search, url, and email ([#1850](https://github.com/dequelabs/axe-core/issues/1850)) ([75d3c8b](https://github.com/dequelabs/axe-core/commit/75d3c8b)) +- **axe.d.ts:** RunOnly.values should not accept a RunOnlyOption ([#1888](https://github.com/dequelabs/axe-core/issues/1888)) ([94b1466](https://github.com/dequelabs/axe-core/commit/94b1466)) +- **build:** add lang query parameter to helpUrl when builing with a locale ([#1909](https://github.com/dequelabs/axe-core/issues/1909)) ([a2f0247](https://github.com/dequelabs/axe-core/commit/a2f0247)) +- **color-contrast:** take into account parent opacity for foreground color ([#1902](https://github.com/dequelabs/axe-core/issues/1902)) ([639c41b](https://github.com/dequelabs/axe-core/commit/639c41b)) +- **is-focusable:** use tabindex attribute instead of property ([#1912](https://github.com/dequelabs/axe-core/issues/1912)) ([aa5314d](https://github.com/dequelabs/axe-core/commit/aa5314d)) +- **is-icon-ligature:** ignore whitespace characters ([#1908](https://github.com/dequelabs/axe-core/issues/1908)) ([77fc838](https://github.com/dequelabs/axe-core/commit/77fc838)) +- **is-ligature-icon:** rename canvas to canvasContext ([#1880](https://github.com/dequelabs/axe-core/issues/1880)) ([604ba4f](https://github.com/dequelabs/axe-core/commit/604ba4f)) +- **link-name:** test role=link when there is no href ([#1921](https://github.com/dequelabs/axe-core/issues/1921)) ([e839c57](https://github.com/dequelabs/axe-core/commit/e839c57)) +- **locales:** fix incompeteMessageFallback to be a string rather than an object ([#1853](https://github.com/dequelabs/axe-core/issues/1853)) ([02a1d8a](https://github.com/dequelabs/axe-core/commit/02a1d8a)) +- **run:** throw error if axe.run is called after a run has started but not completed ([#1914](https://github.com/dequelabs/axe-core/issues/1914)) ([290d125](https://github.com/dequelabs/axe-core/commit/290d125)) +- **server-side-image-map:** return as needs review rather than failure ([#1898](https://github.com/dequelabs/axe-core/issues/1898)) ([ce73af9](https://github.com/dequelabs/axe-core/commit/ce73af9)) +- **tabindex:** don't error when tabindex property is overridden ([#1910](https://github.com/dequelabs/axe-core/issues/1910)) ([e6875ee](https://github.com/dequelabs/axe-core/commit/e6875ee)) +- **td-has-headers:** greatly improve performance of td-has-headers rule ([#1887](https://github.com/dequelabs/axe-core/issues/1887)) ([a588cad](https://github.com/dequelabs/axe-core/commit/a588cad)) +- **typings:** add proper return value to getRule ([#1900](https://github.com/dequelabs/axe-core/issues/1900)) ([0d7c3d2](https://github.com/dequelabs/axe-core/commit/0d7c3d2)) + ## [3.4.0](https://github.com/dequelabs/axe-core/compare/v3.3.2...v3.4.0) (2019-10-18) ### Bug Fixes diff --git a/Gruntfile.js b/Gruntfile.js index 03cd6aa128..8bb5f0ac6e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -13,7 +13,6 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-mocha'); grunt.loadNpmTasks('grunt-parallel'); grunt.loadNpmTasks('grunt-run'); grunt.loadTasks('build/tasks'); @@ -337,9 +336,6 @@ module.exports = function(grunt) { } } }, - mocha: testConfig(grunt, { - reporter: grunt.option('reporter') || 'Spec' - }), connect: { test: { options: { @@ -353,14 +349,21 @@ module.exports = function(grunt) { npm_run_imports: { cmd: 'node', args: ['./build/imports-generator'] + }, + npm_run_testHeadless: { + cmd: 'npm', + args: ['run', 'test:headless'] } } }); - grunt.registerTask('default', ['build']); - + grunt.registerTask('translate', [ + 'pre-build', + 'validate', + 'concat:commons', + 'add-locale' + ]); grunt.registerTask('pre-build', ['clean', 'run:npm_run_imports']); - grunt.registerTask('build', [ 'pre-build', 'validate', @@ -371,45 +374,20 @@ module.exports = function(grunt) { 'uglify', 'aria-supported' ]); - - grunt.registerTask('test', [ + grunt.registerTask('prepare', [ 'build', 'file-exists', 'testconfig', 'fixture', - 'connect', - 'mocha', - 'parallel' + 'connect' ]); - - grunt.registerTask('ci-build', [ - 'build', - 'testconfig', - 'fixture', - 'connect', + grunt.registerTask('default', ['build']); + grunt.registerTask('dev', ['prepare', 'watch']); + grunt.registerTask('test-fast', ['prepare', 'run:npm_run_testHeadless']); + grunt.registerTask('test', [ + 'prepare', + 'run:npm_run_testHeadless', 'parallel' ]); - - grunt.registerTask('test-fast', [ - 'build', - 'testconfig', - 'fixture', - 'connect', - 'mocha' - ]); - - grunt.registerTask('translate', [ - 'pre-build', - 'validate', - 'concat:commons', - 'add-locale' - ]); - - grunt.registerTask('dev', [ - 'build', - 'testconfig', - 'fixture', - 'connect', - 'watch' - ]); + grunt.registerTask('ci-build', ['prepare', 'parallel']); }; diff --git a/README.md b/README.md index 6db04f1f25..2bbe914c96 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ Axe can be built using your local language. To do so, a localization file must b This will create a new build for axe, called `axe..js` and `axe..min.js`. If you want to build localized versions, simply pass in `--all-lang` instead. -To create a new translation for axe, start by running `grunt translate --lang=`. This will create a json file fin the `./locales` directory, with the default English text in it for you to translate. We welcome any localization for axe-core. For details on how to contribute, see the Contributing section below. +To create a new translation for axe, start by running `grunt translate --lang=`. This will create a json file fin the `./locales` directory, with the default English text in it for you to translate. We welcome any localization for axe-core. For details on how to contribute, see the Contributing section below. For details on the message syntax, see [Check Message Template](/docs/check-message-template.md). To update existing translation file, re-run `grunt translate --lang=`. This will add new messages used in English and remove messages which were not used in English. @@ -116,7 +116,7 @@ axe.configure({ 'aria-errormessage': { // Note: doT (https://github.com/olado/dot) templates are supported here. fail: - 'Der Wert der aria-errormessage {{~it.data:value}} `{{=value}}{{~}}` muss eine Technik verwenden, um die Message anzukündigen (z. B., aria-live, aria-describedby, role=alert, etc.).' + 'Der Wert der aria-errormessage ${data.values}` muss eine Technik verwenden, um die Message anzukündigen (z. B., aria-live, aria-describedby, role=alert, etc.).' } // ... } diff --git a/bower.json b/bower.json index a9dac51b8c..a6ff446dd6 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "axe-core", - "version": "3.4.0", + "version": "3.4.1", "contributors": [ { "name": "David Sturley", diff --git a/build/configure.js b/build/configure.js index a0e1fe09d5..f9df67f474 100644 --- a/build/configure.js +++ b/build/configure.js @@ -6,7 +6,8 @@ var clone = require('clone'); var dot = require('@deque/dot'); var templates = require('./templates'); var buildManual = require('./build-manual'); -var entities = new (require('html-entities')).AllHtmlEntities(); +var entities = new (require('html-entities').AllHtmlEntities)(); +var dotRegex = /\{\{.+?\}\}/g; var descriptionHeaders = '| Rule ID | Description | Impact | Tags | Enabled by default | Failures | Needs Review |\n| :------- | :------- | :------- | :------- | :------- | :------- | :------- |\n'; @@ -59,7 +60,10 @@ function buildRules(grunt, options, commons, callback) { Object.keys(result.messages).forEach(function(key) { // only convert to templated function for strings // objects handled later in publish-metadata.js - if (typeof result.messages[key] !== 'object') { + if ( + typeof result.messages[key] !== 'object' && + dotRegex.test(result.messages[key]) + ) { result.messages[key] = dot .template(result.messages[key]) .toString(); @@ -67,7 +71,7 @@ function buildRules(grunt, options, commons, callback) { }); } //TODO this is actually failureSummaries, property name should better reflect that - if (result.failureMessage) { + if (result.failureMessage && dotRegex.test(result.failureMessage)) { result.failureMessage = dot.template(result.failureMessage).toString(); } return result; @@ -86,7 +90,10 @@ function buildRules(grunt, options, commons, callback) { function getIncompleteMsg(summaries) { var result = {}; summaries.forEach(function(summary) { - if (summary.incompleteFallbackMessage) { + if ( + summary.incompleteFallbackMessage && + dotRegex.test(summary.incompleteFallbackMessage) + ) { result = dot.template(summary.incompleteFallbackMessage).toString(); } }); diff --git a/build/tasks/validate.js b/build/tasks/validate.js index 802c279f93..b8eeb987e3 100644 --- a/build/tasks/validate.js +++ b/build/tasks/validate.js @@ -37,8 +37,6 @@ function hasMultipleOutcomes(messages) { switch (key) { case 'pass': case 'fail': - return typeof messages[key] === 'string'; - case 'incomplete': return ['string', 'object'].includes(typeof messages[key]); diff --git a/build/test/get-test-urls.js b/build/test/get-test-urls.js new file mode 100644 index 0000000000..d267895db8 --- /dev/null +++ b/build/test/get-test-urls.js @@ -0,0 +1,38 @@ +const globby = require('globby'); + +const getTestUrls = async (host = `localhost`, port = `9876`) => { + const urls = [ + /** + * Unit tests -> Core + */ + `http://${host}:${port}/test/core/`, + /** + * Unit tests -> Checks + */ + `http://${host}:${port}/test/checks/`, + /** + * Unit tests -> Matches + */ + `http://${host}:${port}/test/rule-matches/`, + /** + * Unit tests -> Commons + */ + `http://${host}:${port}/test/commons/`, + /** + * Integration tests -> rules + */ + `http://${host}:${port}/test/integration/rules`, + /** + * Integration tests -> full + */ + ...( + await globby([ + 'test/integration/full/**/*.html', + '!test/integration/full/**/frames/**/*.html' + ]) + ).map(file => `http://${host}:${port}/${file}`) + ]; + return urls; +}; + +module.exports = getTestUrls; diff --git a/build/test/headless.js b/build/test/headless.js new file mode 100644 index 0000000000..76c90c01a3 --- /dev/null +++ b/build/test/headless.js @@ -0,0 +1,26 @@ +/** + * Note: + * For this to be run via `npm run test:headless` ensure server is running + */ +const getTestUrls = require('./get-test-urls'); +const { runner } = require('mocha-headless-chrome'); + +/** + * Run headless tests + */ +(async function runTests() { + const testUrls = await getTestUrls(); + for (const url of testUrls) { + const options = { + file: url + }; + const { result } = await runner(options); + + /** + * Stop test execution on failure, for early feedback + */ + if (result.stats.failures > 0) { + process.exit(1); + } + } +})(); diff --git a/doc/aria-supported.md b/doc/aria-supported.md index 7698a5d233..9dd0462014 100644 --- a/doc/aria-supported.md +++ b/doc/aria-supported.md @@ -11,4 +11,3 @@ For a detailed description about how accessibility support is decided, see [How | aria-attribute | axe-core support | | ---------------- | ---------------- | | aria-describedat | No | -| aria-details | No | diff --git a/doc/check-message-template.md b/doc/check-message-template.md new file mode 100644 index 0000000000..b56408322d --- /dev/null +++ b/doc/check-message-template.md @@ -0,0 +1,124 @@ +# Check Message Template + +Axe-core uses a custom template to handle dynamic check messages (messages that use the `data` property to output values or to determine which message to display). The structure for the messages is as follows: + +## Simple Message + +A simple message is just a string that doesn't use the `data` property. Most checks uses this format. + +```json +{ + "messages": { + "pass": "Simple message for a passing check" + } +} +``` + +## Message with Data + +A message can also use the `data` property to output information from the check. If `data` is a String, Boolean, or Number, you can use the syntax `${data}` to have the message output the value of the `data` property. + +```js +// check.js +this.data(10); + +// check.json +{ + "messages": { + "pass": "Passed with a value of ${data}" + // => "Passed with a value of 10" + } +} +``` + +If `data` is an object, you can access properties of the object using the syntax `${data.propName}`. + +```js +// check.js +this.data({ + contrast: '3:1', + fontSize: '12px' +}); + +// check.json +{ + "messages": { + "fail": "Color-contrast failed with a contrast of ${data.contrast} and font size of ${data.fontSize}" + // => "Color-contrast failed with a contrast of 3:1 and font size of 12px" + } +} +``` + +## Singular and Plural Messages + +If the message needs to to know how many items are in the `data` property to determine the type of language to use (singular or plural), you can structure the message to use `singular` and `plural` properties. Use the syntax `${data.values}` to have the message output a comma-separated list of the items (`data.values` is provided by the template code for you). + +```js +// check.js +this.data(['item1', 'item2']); + +// check.json +{ + "messages": { + "fail": { + "singular": "Attribute ${data.values} is not allowed", + "plural": "Attributes: ${data.values} are not allowed" + } + // => Attributes: item1, item2 are not allowed + } +} +``` + +## Message Determined by Data + +Lastly, a message can use the `data` property to determine which message to display. Structure the message to use properties whose keys are the possible values of `data.messageKey`. You should also provide a `default` message that will be displayed if `messageKey` is not set. + +```js +// check.js +this.data({ + messageKey: 'imgNode' +}); + +// check.json +{ + "messages": { + "incomplete": { + "default": "Color-contrast could not be determined" + "bgImage": "Element's background color could not be determined due to a background image", + "imgNode": "Element's background color could not be determined because element contains an image node" + } + // => Element's background color could not be determined because element contains an image node + } +} +``` + +The messages can still use the syntax `${data.propName}` to access other properties on the `data` property. + +## Migrating From doT.js Template in Translations + +Axe-core use to use doT.js for it's temple library. To migrate from doT.js in a translation file, do the following: + +- If the message used `{{=it.data}}` or `{{=it.data.propName}}`, change the message to use the syntax `${data}` or `${data.propName}`. + +```diff +{ + "messages": { +- "incomplete": "Check that the