diff --git a/.circleci/config.yml b/.circleci/config.yml index 7b3aa4221..63acb7fc2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ defaults: &defaults unix_box: &unix_box docker: - - image: cimg/node:18.18-browsers + - image: cimg/node:22.16-browsers unix_nightly_box: &unix_nightly_box docker: @@ -13,7 +13,7 @@ unix_nightly_box: &unix_nightly_box orbs: puppeteer: threetreeslight/puppeteer@0.1.2 - browser-tools: circleci/browser-tools@1.4.8 + browser-tools: circleci/browser-tools@1.5.1 set_npm_auth: &set_npm_auth run: npm config set "//registry.npmjs.org/:_authToken" $NPM_AUTH @@ -61,13 +61,14 @@ jobs: paths: - node_modules - # Build and cache axe.js + # Build and cache built files build_unix: <<: *defaults <<: *unix_box steps: - checkout - <<: *restore_dependency_cache_unix + - run: npm run prepare - run: npm run build - save_cache: key: v9-cache-build-<< pipeline.git.revision >> @@ -183,8 +184,9 @@ jobs: - run: name: Install Firefox Nightly command: | - wget -O firefox-nightly.tar.bz2 "https://download.mozilla.org/?product=firefox-nightly-latest-ssl&os=linux64&lang=en-US" - tar xf firefox-nightly.tar.bz2 + # Assumes Firefox >= 135; in earlier versions, this resolves to a .tar.bz2 instead + wget -O firefox-nightly.tar.xz "https://download.mozilla.org/?product=firefox-nightly-latest-ssl&os=linux64&lang=en-US" + tar xf firefox-nightly.tar.xz - run: name: Set Environment Variable command: echo "export FIREFOX_NIGHTLY_BIN=$(pwd)/firefox/firefox-bin" >> $BASH_ENV diff --git a/.eslintignore b/.eslintignore index ca1d987e9..df541335e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -7,5 +7,6 @@ doc/api/* doc/examples/jest_react/*.js lib/core/imports/*.js +lib/core/utils/uuid.js axe.js -axe.min.js \ No newline at end of file +axe.min.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index b269cdee5..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,158 +0,0 @@ -module.exports = { - root: true, - extends: ['prettier'], - parserOptions: { - ecmaVersion: 2023 - }, - env: { - node: true, - es6: true - }, - globals: { - axe: true, - Promise: true, - a11yEngine: true - }, - rules: { - 'no-bitwise': 2, - camelcase: 2, - curly: 2, - eqeqeq: 2, - 'guard-for-in': 2, - 'wrap-iife': [2, 'any'], - 'no-use-before-define': [ - 2, - { - functions: false - } - ], - 'new-cap': 2, - 'no-caller': 2, - 'no-empty': 2, - 'no-new': 2, - 'no-plusplus': 0, - 'no-undef': 2, - 'no-unused-vars': 2, - strict: 0, - 'max-params': [2, 6], - 'max-depth': [2, 5], - 'max-len': 0, - semi: 0, - 'no-cond-assign': 0, - 'no-debugger': 2, - 'no-eq-null': 0, - 'no-eval': 2, - 'no-unused-expressions': 0, - 'block-scoped-var': 0, - 'no-iterator': 0, - 'linebreak-style': 0, - 'no-loop-func': 0, - 'no-multi-str': 0, - 'no-proto': 0, - 'no-script-url': 0, - 'dot-notation': 2, - 'no-new-func': 0, - 'no-new-wrappers': 0, - 'no-shadow': 2, - 'no-restricted-syntax': [ - 'error', - { - selector: 'MemberExpression[property.name=tagName]', - message: "Don't use node.tagName, use node.nodeName instead." - }, - { - // node.attributes can be clobbered so is unsafe to use - // @see https://github.com/dequelabs/axe-core/pull/1432 - selector: - 'MemberExpression[object.name=node][property.name=attributes]', - message: - "Don't use node.attributes, use node.hasAttributes() or axe.utils.getNodeAttributes(node) instead." - } - ] - }, - overrides: [ - { - files: ['lib/**/*.js'], - excludedFiles: ['lib/core/reporters/**/*.js', 'lib/**/*-after.js'], - parserOptions: { - sourceType: 'module' - }, - env: { - // do not access global window properties without going through window - browser: false, - es6: true - }, - globals: { - window: true, - document: true - }, - rules: { - 'func-names': [2, 'as-needed'], - 'prefer-const': 2, - 'no-use-before-define': 'off' - } - }, - { - // after functions and reporters will not be run inside the same context as axe.run so should not access browser globals that require context specific information (window.location, window.getComputedStyles, etc.) - files: ['lib/**/*-after.js', 'lib/core/reporters/**/*.js'], - parserOptions: { - sourceType: 'module' - }, - env: { - browser: false - }, - globals: {}, - rules: { - 'func-names': [2, 'as-needed'], - 'prefer-const': 2, - 'no-use-before-define': 'off' - } - }, - { - // polyfills are mostly copy-pasted from sources so we don't control their styling - files: [ - 'lib/core/imports/polyfills.js', - 'lib/core/utils/pollyfill-elements-from-point.js' - ], - env: { - browser: false - }, - rules: { - 'func-names': 0, - 'no-bitwise': 0, - curly: 0, - eqeqeq: 0 - } - }, - { - files: ['test/act-rules/**/*.js', 'test/aria-practices/**/*.js'], - env: { - mocha: true - } - }, - { - files: ['test/**/*.js'], - excludedFiles: ['test/act-rules/**/*.js', 'test/aria-practices/**/*.js'], - parserOptions: { - ecmaVersion: 2021 - }, - env: { - browser: true, - es6: false, - mocha: true - }, - globals: { - assert: true, - helpers: true, - checks: true, - sinon: true - }, - plugins: ['mocha-no-only'], - rules: { - 'new-cap': 0, - 'no-use-before-define': 0, - 'mocha-no-only/mocha-no-only': ['error'] - } - } - ] -}; diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b7ba1c567..4ed3712ff 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -14,6 +14,8 @@ updates: update-types: - 'minor' - 'patch' + cooldown: + default-days: 7 - package-ecosystem: 'npm' directory: '/' @@ -32,13 +34,27 @@ updates: versions: ['>=9.0.0'] - dependency-name: 'chai' versions: ['>=5.0.0'] + - dependency-name: 'conventional-commits-parser' + versions: ['>=6.0.0'] # Prevent Webpack error caused by v0.11+ of esbuild # @see https://github.com/dequelabs/axe-core/issues/3771 - dependency-name: 'esbuild' versions: ['>=0.11.0'] + # Prevent colorjs.io issue caused by >v0.4.3 + # @see https://github.com/dequelabs/axe-core/issues/4428 + - dependency-name: 'colorjs.io' + versions: ['>0.4.3'] + # Still need to support node 18 + - dependency-name: 'glob' + versions: ['>=11.0.0'] + # Use node 4 types for backward compatibility + - dependency-name: '@types/node' + versions: ['>=5.0.0'] groups: # Any updates not caught by the group config will get individual PRs npm-low-risk: update-types: - 'minor' - 'patch' + cooldown: + default-days: 7 diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 74898ee2d..ac7402759 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -1,19 +1,27 @@ name: Formatter -on: [pull_request] +on: + pull_request: + branches: + - develop jobs: prettier: + # This conditional prevents running the job on PRs from forks; won't + # have permissions to commit changes, so the job would fail if it ran. + # PRs from forks will instead rely on failing the fmt_check job in test.yml + if: github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest + timeout-minutes: 5 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: ${{ github.event.pull_request.head.ref }} - name: Install dependencies run: npm ci - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: - node-version: 16 + node-version-file: .nvmrc cache: 'npm' # Workflows are not allowed to edit workflows. As result, we need to prevent Prettier from formatting them. - name: Prevent workflows from being formatted @@ -21,6 +29,6 @@ jobs: - run: npm run fmt # Prevent the prettierignore change from being committed. - run: git checkout .prettierignore - - uses: stefanzweifel/git-auto-commit-action@8756aa072ef5b4a080af5dc8fef36c5d586e521d # tag=v5 + - uses: stefanzweifel/git-auto-commit-action@778341af668090896ca464160c2def5d1d1a3eb0 # tag=v5 with: commit_message: ':robot: Automated formatting fixes' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ba0714f1c..a2b765b7f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,12 +7,12 @@ jobs: name: Create release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: - node-version: 16 + node-version-file: .nvmrc cache: 'npm' - name: Run release script and open PR run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5cbf7d464..704b38424 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,12 +12,13 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 with: - node-version: 20 + node-version-file: .nvmrc cache: 'npm' - run: npm ci + - run: npm run prepare - run: npm run build - uses: actions/upload-artifact@v4 with: @@ -25,19 +26,31 @@ jobs: path: axe.js retention-days: 1 + fmt_check: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 + with: + node-version-file: .nvmrc + cache: 'npm' + - run: npm ci + - run: npm run fmt:check + test_node: strategy: matrix: - node: [6, 18, 20] + node: [6, 18, 20, 22] runs-on: ubuntu-latest timeout-minutes: 5 needs: build steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 with: node-version: ${{ matrix.node}} - - uses: actions/download-artifact@v4.1.7 + - uses: actions/download-artifact@v5 with: name: axe-core - - run: npm run test:node + - run: npm run test:node \ No newline at end of file diff --git a/.github/workflows/update-generated-files.yaml b/.github/workflows/update-generated-files.yaml index 2277e82c6..f10368fa9 100644 --- a/.github/workflows/update-generated-files.yaml +++ b/.github/workflows/update-generated-files.yaml @@ -9,13 +9,17 @@ env: BRANCH_NAME: sync-generated-files jobs: - update_genereated_files: + update_generated_files: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 + - uses: actions/setup-node@v5 + with: + node-version-file: .nvmrc + cache: 'npm' - name: Build run: | @@ -28,7 +32,7 @@ jobs: changes=$(git status --porcelain) # see https://unix.stackexchange.com/a/509498 echo $changes | grep . && echo "Changes detected" || echo "No changes" - echo ::set-output name=changes::"$changes" + echo "changes=$changes" >> "$GITHUB_OUTPUT" - name: Check branch exists id: branchExists @@ -36,7 +40,7 @@ jobs: run: | exists=$(git ls-remote --heads origin $BRANCH_NAME) echo $exists | grep . && echo "Branch '$BRANCH_NAME' already exists on remote" || echo "Branch does not exists in remote" - echo ::set-output name=exists::"$exists" + echo "exists=$exists" >> "$GITHUB_OUTPUT" - name: Create pull request if: ${{ steps.changes.outputs.changes && !steps.branchExists.outputs.exists }} diff --git a/.gitignore b/.gitignore index f72967491..9aacdeb71 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,3 @@ typings/axe-core/axe-core-tests.js # doc doc/rule-descriptions.*.md - diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..2bd5a0a98 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..e189436e9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "attach", + "name": "Attach to Karma test:debug", + "address": "localhost", + "port": 9765, // keep in sync with debugPort in karma.conf.js + "webRoot": "${workspaceFolder}", + "sourceMaps": true, + "sourceMapPathOverrides": { + "*": "${webRoot}/*", + "base/*": "${webRoot}/*" + } + } + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d692677a2..a65031166 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,6 +21,46 @@ The files in this project are formatted by [Prettier](https://prettier.io/) and npm run eslint ``` +### When to use HTMLElement vs Virtual Node + +Axe-core uses an internal copy of the HTML page for most things internal to axe-core (called the Virtual Tree). Each HTML element on the page has an equivalent [Virtual Node](./lib/core/base/virtual-node) element, which allows us to cache or normalize information about the HTML element without mutating the actual DOM node. + +Typically we use the Virtual Node when possible, but understand that's not always possible (such as accessing DOM only APIs like `getRootNode` or `getBoundingClientRect`). Furthermore, any function within the [utils directory](./lib/core/utils/) should not use Virtual Nodes. The reason for this is that using Virtual Nodes requires that axe first be [setup](./lib/core/public/setup.js) and create the Virtual Tree. For the most part, util functions are functions that can be run when no Virtual Tree exists. + +### Directory Structure + +Axe-core + +- `standards` - Data objects of the HTML, WCAG, and ARIA standards and specs. +- `core/base` - Defines many of the internal object structures for axe-core (rules, checks, virtual node, etc.). +- `core/imports` - Polyfills or imports from node modules. +- `core/public` - Functions for how axe is run or configured. +- `core/reporters` - The different reporters that configure what data is reported from a run. +- `core/utils` - Utility functions that can be run without needing to set up the Virtual Tree. +- `commons/aria` - Functions that are used to validate ARIA spec or implement ARIA calculations (role, value, etc.). +- `commons/color` - Functions that are used to calculate the foreground or background color of an element. +- `commons/dom` - Functions that access information about the page, DOM, nodes, or its visual state. +- `commons/forms` - Functions for helping with forms and their associated inputs. +- `commons/matches` - Functions used to match a virtual node against a special matcher object. +- `commons/math` - Math functions mainly used for target size and position calculations. +- `commons/standards` - Functions for querying information from `standards`. +- `commons/tables` - Functions for helping with data tables. +- `commons/text` - Functions for calculating the accessible name of an element and handling strings or text related attributes. +- `rules` - JSON metadata files for each axe-core rule as well as their associated matches functions. +- `checks` - JSON metadata files for each axe-core check as well as their associated evaluate functions. + +### Import Requirements + +Which functions can be imported and how they can be imported depends on where you are trying to import them from. The following is a list of directories and their import restrictions: + +- `standards` - Shouldn't use imports as they are just hard coded data objects. +- `core/utils` - Can import other `core/utils`, `core`, `core/base`, or `standards` functions by direct file path (no import from index files). +- `core/public` - Can import other `core/public` by direct file path, or any import allowed by `core/utils` by direct file path. +- `core/imports` - The only files allowed to import from node modules, but shouldn't import from any other directory. +- `core/reporters` Can import from `core/utils` by import from index files. +- `commons` - Can import other `commons` by direct file path, or any import allowed by `core/utils` by import from index files. +- `checks` and `rules` - Can import from any directory by import from index files. + ### Shadow DOM For any proposed changes to rules, checks, commons, or other APIs to be accepted in axe-core, your code must support open Shadow DOM. See [API.md](./doc/API.md) and the [developer guide](./doc/developer-guide.md) for documentation on the available methods and test utilities. You can also look at existing tests for examples using our APIs. @@ -162,7 +202,7 @@ If you need to debug the unit tests in a browser, you can run: npm run test:debug ``` -This will start the Karma server and open up the Chrome browser. Click the `Debug` button to start debugging the tests. You can also navigate to the listed URL in your browser of choice to debug tests using that browser. +This will start the Karma server and open up the Chrome browser. Click the `Debug` button to start debugging the tests. You can either use that browser's debugger or attach an external debugger on port 9765; [a VS Code launch profile](./.vscode/launch.json) is provided. You can also navigate to the listed URL in your browser of choice to debug tests using that browser. Because the amount of tests is so large, it's recommended to debug only a specific set of unit tests rather than the whole test suite. You can use the `testDirs` argument when using the debug command and pass a specific test directory. The test directory names are the same as those used for `test:unit:*`: diff --git a/Gruntfile.js b/Gruntfile.js index 47d9e0787..3b9872799 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -23,9 +23,13 @@ module.exports = function (grunt) { }); } else if (grunt.option('all-lang')) { var localeFiles = require('fs').readdirSync('./locales'); - langs = localeFiles.map(function (file) { - return '.' + file.replace('.json', ''); - }); + langs = localeFiles + .filter(function (file) { + return !file.startsWith('_') && file.endsWith('.json'); + }) + .map(function (file) { + return '.' + file.replace('.json', ''); + }); langs.unshift(''); // Add default } else { langs = ['']; diff --git a/README.md b/README.md index f65c5bd43..505643baa 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,12 @@ # a11y-engine-axe-core -This is a fork of public Axe core repo: -https://github.com/dequelabs/axe-core +[![License](https://img.shields.io/npm/l/axe-core.svg?color=c41)](LICENSE) +[![Version](https://img.shields.io/npm/v/axe-core.svg)](https://www.npmjs.com/package/axe-core) +[![NPM downloads](https://img.shields.io/npm/dw/axe-core.svg?color=080)![](https://img.shields.io/npm/dy/axe-core.svg?color=080&label=)](https://npm-stat.com/charts.html?package=axe-core&from=2017-01-01) +[![Commits](https://img.shields.io/github/commit-activity/y/dequelabs/axe-core.svg)](https://github.com/dequelabs/axe-core/commits/develop) +[![GitHub contributors](https://img.shields.io/github/contributors/dequelabs/axe-core.svg?color=080)](https://github.com/dequelabs/axe-core/graphs/contributors) +[![Join our Slack chat](https://img.shields.io/badge/slack-chat-purple.svg?logo=slack)](https://accessibility.deque.com/axe-community) +[![Package Quality](https://npm.packagequality.com/shield/axe-core.svg)](https://packagequality.com/#?package=axe-core) ## Pull latest changes from public axe-core repo @@ -12,11 +17,169 @@ cd a11y-engine-axe-core # Add public axe-core repo as remote public git remote add public git@github.com:dequelabs/axe-core.git -# Creates a merge commit -git pull public master +Axe-core has different types of rules, for WCAG 2.0, 2.1, 2.2 on level A, AA and AAA as well as a number of best practices that help you identify common accessibility practices like ensuring every page has an `h1` heading, and to help you avoid "gotchas" in ARIA like where an ARIA attribute you used will get ignored. The complete list of rules, grouped WCAG level and best practice, can be found in [doc/rule-descriptions.md](./doc/rule-descriptions.md). # push the changes to internal private repo git push origin master ``` -Axe-core readme: https://github.com/dequelabs/axe-core/blob/v4.9.0/README.md +Now include the javascript file in each of your iframes in your fixtures or test systems: + +```html + +``` + +Now insert calls at each point in your tests where a new piece of UI becomes visible or exposed: + +```js +axe + .run() + .then(results => { + if (results.violations.length) { + throw new Error('Accessibility issues found'); + } + }) + .catch(err => { + console.error('Something bad happened:', err.message); + }); +``` + +## Philosophy + +The web can only become an accessible, inclusive space if developers are empowered to take responsibility for accessibility testing and accessible coding practices. + +Automated accessibility testing is a huge timesaver, it doesn't require special expertise, and it allows teams to focus expert resources on the accessibility issues that really need them. Unfortunately, most accessibility tools are meant to be run on sites and applications that have reached the end of the development process and often don't give clear or consistent results, causing frustration and delays just when you thought your product was ready to ship. + +Axe was built to reflect how web development actually works. It works with all modern browsers, tools, and testing environments a dev team might use. With axe, accessibility testing can be performed as part of your unit testing, integration testing, browser testing, and any other functional testing your team already performs on a day-to-day basis. Building accessibility testing into the early development process saves time, resources, and all kinds of frustration. + +## About axe - our Manifesto + +- Axe is open source. +- It returns zero false positives (bugs notwithstanding). +- It's designed to work on all modern browsers and with whatever tools, frameworks, libraries and environments you use today. +- It's actively supported by [Deque Systems](https://www.deque.com), a major accessibility vendor. +- It integrates with your existing functional/acceptance automated tests. +- It automatically determines which rules to run based on the evaluation context. +- Axe supports in-memory fixtures, static fixtures, integration tests, and iframes of infinite depth. +- Axe is highly configurable. + +## Supported Browsers + +The [axe-core API](doc/API.md) fully supports the following browsers: + +- Microsoft Edge v40 and above +- Google Chrome v42 and above +- Mozilla Firefox v38 and above +- Apple Safari v7 and above +- Internet Explorer v11 (DEPRECATED) + +Support means that we will fix bugs and attempt to test each browser regularly. Only Chrome and Firefox are currently tested on every pull request. + +There is limited support for JSDOM. We will attempt to make all rules compatible with JSDOM but where this is not possible, we recommend turning those rules off. Currently the `color-contrast` rule is known not to work with JSDOM. + +We can only support environments where features are either natively supported or polyfilled correctly. We do not support the deprecated v0 Shadow DOM implementation. + +## Contents of the API Package + +The [axe-core API](doc/API.md) package consists of: + +- `axe.js` - the JavaScript file that should be included in your web site under test (API) +- `axe.min.js` - a minified version of the above file + +## Localization + +Axe can be built using your local language. To do so, a localization file must be added to the `./locales` directory. This file must be named in the following manner: `.json`. To build axe using this locale, instead of the default, run axe with the `--lang` flag, like so: + +`grunt build --lang=nl` + +or equivalently: + +`npm run build -- --lang=nl` + +This will create a new build for axe, called `axe..js` and `axe..min.js`. If you want to build all localized versions, simply pass in `--all-lang` instead. If you want to build multiple localized versions (but not all of them), you can pass in a comma-separated list of languages to the `--lang` flag, like `--lang=nl,ja`. + +To create a new translation for axe, start by running `grunt translate --lang=`. This will create a json file in the `./locales` directory, with the default English text in it for you to translate. Alternatively, you could copy `./locales/_template.json`. 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](/doc/check-message-template.md). + +To update an 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. + +Additionally, locale can be applied at runtime by passing a `locale` object to `axe.configure()`. The locale object must be of the same shape as existing locales in the `./locales` directory. For example: + +```js +axe.configure({ + locale: { + lang: 'de', + rules: { + accesskeys: { + help: 'Der Wert des accesskey-Attributes muss einzigartig sein.' + } + // ... + }, + checks: { + abstractrole: { + fail: 'Abstrakte ARIA-Rollen dürfen nicht direkt verwendet werden.' + }, + 'aria-errormessage': { + // Note: doT (https://github.com/olado/dot) templates are supported here. + fail: '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.).' + } + // ... + } + } +}); +``` + +### Supported Locales + +Axe-core supports the following locales. Do note that since locales are contributed by our community, they are not guaranteed to include all translations needed in a release. + +- Basque +- Chinese (Simplified) +- Chinese (Traditional) +- Danish +- Dutch +- French +- German +- Greek +- Hebrew +- Italian +- Japanese +- Korean +- Norwegian (Bokmål) +- Polish +- Portuguese (Brazilian) +- Spanish + +## Updates & Security + +Axe-core has a new minor release every 3 to 5 months, which usually introduces new rules and features. We recommend scheduling time to upgrade to these versions. Security updates will be made available for minor version lines up to **18 months old**. + +- See [release and support](doc/release-and-support.md) for details on the frequency of releases, long-term support and recommendations on upgrading axe-core. +- See [backward compatibility](doc/backwards-compatibility-doc.md) for details on the types of changes different releases may introduce. + +## Deque Trademarks Policy + +DEQUE, DEQUELABS, AXE®, and AXE-CORE® are trademarks of Deque Systems, Inc. Use of the Deque trademarks must be in accordance with [Deque's trademark policy](https://www.deque.com/legal/trademarks/). + +## Supported ARIA Roles and Attributes. + +Refer [axe-core ARIA support](./doc/aria-supported.md) for a complete list of ARIA supported roles and attributes by axe. + +## Contributing + +Read the [Proposing Axe-core Rules guide](./doc/rule-proposal.md) + +Read the [documentation on the architecture](./doc/developer-guide.md) + +Read the [documentation on contributing](CONTRIBUTING.md) + +## Projects using axe-core + +[List of projects using axe-core](doc/projects.md) + +## Acknowledgements + +Thanks to Marat Dulin for his [css-selector-parser](https://www.npmjs.com/package/css-selector-parser) implementation which is included for shadow DOM support. Another thank you to the [Slick Parser](https://github.com/mootools/slick/blob/master/Source/Slick.Parser.js) implementers for their contribution, we have used some of their algorithms in our shadow DOM support code. Thanks to Lea Verou and Chris Lilley for their [colorjs.io](https://colorjs.io/) library which we have used for converting between color formats. + +## Licenses + +Axe-core is distributed under the [Mozilla Public License, version 2.0](LICENSE). It comes bundled with several dependencies which are distributed under their own terms. (See [LICENSE-3RD-PARTY.txt](LICENSE-3RD-PARTY.txt)) diff --git a/axe.d.ts b/axe.d.ts index 8ecd9de75..786b30a23 100644 --- a/axe.d.ts +++ b/axe.d.ts @@ -70,16 +70,19 @@ declare namespace axe { | LabelledShadowDomSelector | LabelledFramesSelector; type SelectorList = Array | NodeList; + type ContextProp = Selector | SelectorList; type ContextObject = | { - include: Selector | SelectorList; - exclude?: Selector | SelectorList; + include: ContextProp; + exclude?: ContextProp; } | { - exclude: Selector | SelectorList; - include?: Selector | SelectorList; + exclude: ContextProp; + include?: ContextProp; }; - type ElementContext = Selector | SelectorList | ContextObject; + type ContextSpec = ContextProp | ContextObject; + /** Synonym to ContextSpec */ + type ElementContext = ContextSpec; type SerialSelector = | BaseSelector @@ -140,15 +143,19 @@ declare namespace axe { iframes?: boolean; elementRef?: boolean; frameWaitTime?: number; - preload?: boolean; + preload?: boolean | PreloadOptions; performanceTimer?: boolean; pingWaitTime?: number; } + interface PreloadOptions { + assets: string[]; + timeout?: number; + } interface AxeResults extends EnvironmentData { toolOptions: RunOptions; passes: Result[]; violations: Result[]; - incomplete: Result[]; + incomplete: IncompleteResult[]; inapplicable: Result[]; } interface Result { @@ -160,6 +167,9 @@ declare namespace axe { tags: TagValue[]; nodes: NodeResult[]; } + interface IncompleteResult extends Result { + error?: Omit; + } interface NodeResult { html: string; impact?: ImpactValue; @@ -197,6 +207,21 @@ declare namespace axe { fail: string | { [key: string]: string }; incomplete?: string | { [key: string]: string }; } + interface RuleError { + name: string; + message: string; + stack: string; + ruleId?: string; + method?: string; + cause?: SerialError; + errorNode?: SerialDqElement; + } + interface SerialError { + message: string; + stack: string; + name: string; + cause?: SerialError; + } interface CheckLocale { [key: string]: CheckMessages; } @@ -335,6 +360,9 @@ declare namespace axe { interface DqElement extends SerialDqElement { element: Element; toJSON(): SerialDqElement; + } + interface DqElementConstructor { + new (elm: Element, options?: { absolutePaths?: boolean }): DqElement; mergeSpecs( childSpec: SerialDqElement, parentSpec: SerialDqElement @@ -358,23 +386,27 @@ declare namespace axe { frameContext: FrameContextObject; } - interface RawCheckResult extends Omit { + interface RawCheckResult extends Omit< + CheckResult, + 'relatedNodes' | 'impact' + > { relatedNodes?: Array; + impact?: ImpactValue; } - interface RawNodeResult { + interface RawNodeResult { node: SerialDqElement | DqElement; any: RawCheckResult[]; all: RawCheckResult[]; none: RawCheckResult[]; - impact: ImpactValue | null; + impact: ImpactValue | undefined; result: T; } interface RawResult extends Omit { inapplicable: Array; passes: RawNodeResult<'passed'>[]; - incomplete: RawNodeResult<'incomplete'>[]; + incomplete: RawNodeResult<'cantTell'>[]; violations: RawNodeResult<'failed'>[]; pageLevel: boolean; result: 'failed' | 'passed' | 'incomplete' | 'inapplicable'; @@ -398,6 +430,42 @@ declare namespace axe { boundingClientRect: DOMRect; } + type GridCell = VirtualNode[]; + + interface Grid { + container: VirtualNode | null; + cells: unknown; // opaque implementation detail + boundaries?: DOMRect; + toGridIndex(num: number): number; + getCellFromPoint(point: { x: number; y: number }): GridCell; + loopGridPosition( + gridPosition: DOMRect, + callback: (gridCell: GridCell, pos: { row: number; col: number }) => void + ): void; + getGridPositionOfRect( + rect: { top: number; right: number; bottom: number; left: number }, + margin?: number + ): DOMRect; + } + + interface CustomNodeSerializer { + toSpec: (dqElm: DqElement) => T; + mergeSpecs: (nodeSpec: T, parentFrameSpec: T) => T; + } + + interface NodeSerializer { + update: (serializer: CustomNodeSerializer) => void; + toSpec: (node: Element | VirtualNode) => SerialDqElement; + dqElmToSpec: ( + dqElm: DqElement | SerialDqElement, + options?: RunOptions + ) => SerialDqElement; + mergeSpecs: ( + nodeSpec: SerialDqElement, + parentFrameSpec: SerialDqElement + ) => SerialDqElement; + } + interface Utils { getFrameContexts: ( context?: ElementContext, @@ -406,15 +474,29 @@ declare namespace axe { shadowSelect: (selector: CrossTreeSelector) => Element | null; shadowSelectAll: (selector: CrossTreeSelector) => Element[]; getStandards(): Required; - DqElement: new ( - elm: Element, - options?: { absolutePaths?: boolean } - ) => DqElement; + isContextSpec: (context: unknown) => context is ContextSpec; + isContextObject: (context: unknown) => context is ContextObject; + isContextProp: (context: unknown) => context is ContextProp; + isLabelledFramesSelector: ( + selector: unknown + ) => selector is LabelledFramesSelector; + isLabelledShadowDomSelector: ( + selector: unknown + ) => selector is LabelledShadowDomSelector; + RuleError: new (options: { + error: Error; + ruleId?: string; + method?: string; + errorNode?: SerialDqElement; + }) => RuleError; + serializeError: (error: Error) => SerialError; + DqElement: DqElementConstructor; uuid: ( options?: { random?: Uint8Array | Array }, buf?: Uint8Array | Array, offset?: number ) => string | Uint8Array | Array; + nodeSerializer: NodeSerializer; } interface Aria { @@ -424,6 +506,7 @@ declare namespace axe { interface Dom { isFocusable: (node: Element | VirtualNode) => boolean; isNativelyFocusable: (node: Element | VirtualNode) => boolean; + getNodeGrid: (node: Node | VirtualNode) => Grid; } type AccessibleTextOptions = { diff --git a/bower.json b/bower.json index bff620abe..52beb0a4f 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "axe-core", - "version": "4.9.0", + "version": "4.11.0", "deprecated": true, "contributors": [ { diff --git a/build/check-node-version.js b/build/check-node-version.js index 53f7cce71..ab0672597 100644 --- a/build/check-node-version.js +++ b/build/check-node-version.js @@ -1,8 +1,13 @@ #! /usr/bin/env node -const currentVersion = process.version.replace('v', ''); +const fs = require('fs'); +const path = require('path'); + +const PATH_TO_NVMRC = path.join(__dirname, '..', '.nvmrc'); +const nvmrc = fs.readFileSync(PATH_TO_NVMRC, 'utf8'); +const minimumVersionMajor = parseInt(nvmrc.trim(), 10); -const minimumVersionMajor = 18; +const currentVersion = process.version.replace('v', ''); const currentVersionMajor = parseInt(currentVersion.split('.')[0]); const usesMinimumVersion = currentVersionMajor >= minimumVersionMajor; diff --git a/build/configure.js b/build/configure.js index ca3241fbd..a8a1da734 100644 --- a/build/configure.js +++ b/build/configure.js @@ -16,7 +16,7 @@ var axeVersion = packageJSON.version.substring( ); var descriptionTableHeader = - '| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules |\n| :------- | :------- | :------- | :------- | :------- | :------- |\n'; + '| Rule ID | Description | Impact | Tags | Issue Type | [ACT Rules](https://www.w3.org/WAI/standards-guidelines/act/rules/) |\n| :------- | :------- | :------- | :------- | :------- | :------- |\n'; // prevent striping newline characters from strings (e.g. failure // summaries). must be synced with lib/core/imports/index.js diff --git a/build/rule-generator/questions.js b/build/rule-generator/questions.js index dc5e665d0..28f9a511f 100644 --- a/build/rule-generator/questions.js +++ b/build/rule-generator/questions.js @@ -33,7 +33,10 @@ const validateGetRuleName = async input => { throw new Error(`RULE name conflicts with an existing rule's filename.`); } // 3) ensure no rule id overlaps - const ruleSpecs = await glob(`${directories.rules}/**/*.json`); + const ruleSpecs = await glob(`${directories.rules}/**/*.json`, { + posix: true, + absolute: true + }); const axeRulesIds = ruleSpecs.reduce((out, specPath) => { const spec = require(specPath); out.push(spec.id); @@ -62,7 +65,10 @@ const validateGetCheckName = async input => { ); } // 2) ensure no check filename overlaps - const checkSpecs = await glob(`${directories.checks}/**/*.json`); + const checkSpecs = await glob(`${directories.checks}/**/*.json`, { + posix: true, + absolute: true + }); // cannot use `fs.existsSync` here, as we do not know which category of checks to look under const axeChecksFileNames = checkSpecs.map( f => f.replace('.json', '').split('/').reverse()[0] @@ -71,7 +77,10 @@ const validateGetCheckName = async input => { throw new Error('CHECK name conflicts with an existing filename.'); } // 3) ensure no check id overlaps - const ruleSpecs = await glob(`${directories.rules}/**/*.json`); + const ruleSpecs = await glob(`${directories.rules}/**/*.json`, { + posix: true, + absolute: true + }); const axe = require(directories.axePath); const axeChecksIds = ruleSpecs.reduce((out, specPath) => { const spec = require(specPath); diff --git a/build/tasks/configure.js b/build/tasks/configure.js index f4a3c6d26..19a8372ef 100644 --- a/build/tasks/configure.js +++ b/build/tasks/configure.js @@ -18,9 +18,11 @@ module.exports = function (grunt) { }); this.files.forEach(function (file) { - const match = file.dest.auto.match(/\.([a-z]{2,3})\.js/); - if (match) { - options.locale = match[1]; + // locale will always be the 2nd to last part of the + // filename and in the format of "..js" + const parts = file.dest.auto.split('.'); + if (parts.length > 2) { + options.locale = parts[parts.length - 2]; } buildRules(grunt, options, null, function (result) { diff --git a/doc/API.md b/doc/API.md index ea1f04a44..18f4dbb9c 100644 --- a/doc/API.md +++ b/doc/API.md @@ -1,4 +1,4 @@ -# Axe Javascript Accessibility API +# Axe JavaScript Accessibility API ## Table of Contents @@ -23,7 +23,7 @@ 1. [API Name: axe.setup](#api-name-axesetup) 1. [API Name: axe.teardown](#api-name-axeteardown) 1. [API Name: axe.frameMessenger](#api-name-axeframemessenger) - 1. [API name: axe.runPartial / axe.finishRun](#api-name-axerunpartial-/-axefinishrun) + 1. [API name: axe.runPartial / axe.finishRun](#api-name-axerunpartial--axefinishrun) 1. [Virtual DOM Utilities](#virtual-dom-utilities) 1. [API Name: axe.utils.querySelectorAll](#api-name-axeutilsqueryselectorall) 1. [API Name: axe.utils.getRule](#api-name-axeutilsgetrule) @@ -49,8 +49,8 @@ This section gives a quick description of how to use the axe APIs to analyze web The axe API can be used as part of a broader process that is performed on many, if not all, pages of a website. The API is used to analyze web page content and return a JSON object that lists any accessibility violations found. Here is how to get started: 1. Load page in testing system -2. Optionally, set configuration options for the javascript API (`axe.configure`) -3. Call analyze javascript API (`axe.run`) +2. Optionally, set configuration options for the JavaScript API (`axe.configure`) +3. Call analyze JavaScript API (`axe.run`) 4. Either assert against results or save them for later processing 5. Repeat for any inactive or non-rendered content after making it visible @@ -58,7 +58,7 @@ The axe API can be used as part of a broader process that is performed on many, ### Overview -The axe APIs are provided in the javascript file axe.js. It must be included in the web page under test, as well as each `iframe` under test. Parameters are sent as javascript function parameters. Results are returned in JSON format. +The axe APIs are provided in the JavaScript file axe.js. It must be included in the web page under test, as well as each `iframe` under test. Parameters are sent as JavaScript function parameters. Results are returned in JSON format. ### Full API Reference for Developers @@ -72,7 +72,7 @@ For a full listing of API offered by axe, clone the repository and run `npm run ### Axe-core Tags -Each rule in axe-core has a number of tags. These provide metadata about the rule. Each rule has one tag that indicates which WCAG version / level it belongs to, or if it doesn't it have the `best-practice` tag. If the rule is required by WCAG, there is a tag that references the success criterion number. For example, the `wcag111` tag means a rule is required for WCAG 2 success criterion 1.1.1. +Each rule in axe-core has a number of tags. These provide metadata about the rule. Each rule has one tag that indicates which WCAG version / level it belongs to, or if it doesn't, it has the `best-practice` tag. If the rule is required by WCAG, there is a tag that references the success criterion number. For example, the `wcag111` tag means a rule is required for WCAG 2 success criterion 1.1.1. The `experimental`, `ACT`, `TT`, and `section508` tags are only added to some rules. Each rule with a `section508` tag also has a tag to indicate what requirement in old Section 508 the rule is required by. For example `section508.22.a`. @@ -94,6 +94,8 @@ The `experimental`, `ACT`, `TT`, and `section508` tags are only added to some ru | `TT*.*` | Test ID in Trusted Tester | | `EN-301-549` | Rule required under [EN 301 549](https://www.etsi.org/deliver/etsi_en/301500_301599/301549/03.02.01_60/en_301549v030201p.pdf) | | `EN-9.*` | Section in EN 301 549 listing the requirement | +| `RGAAv4` | Rule required under [RGAA](https://accessibilite.numerique.gouv.fr/methode/criteres-et-tests/) | +| `RGAA-*.*.*` | Section in RGAA listing the requirement | | `experimental` | Cutting-edge rules, disabled by default | | `cat.*` | Category mappings used by Deque (see below) | @@ -146,7 +148,7 @@ In this example, we pass in the WCAG 2 A and AA tags into `axe.getRules` to retr ```js [ { - description: "Ensures elements of image maps have alternate text", + description: "Ensure elements of image maps have alternate text", help: "Active elements must have alternate text", helpUrl: "https://dequeuniversity.com/rules/axe/3.5/area-alt?application=axeAPI", ruleId: "area-alt", @@ -162,7 +164,7 @@ In this example, we pass in the WCAG 2 A and AA tags into `axe.getRules` to retr actIds: ['c487ae'] }, { - description: "Ensures ARIA attributes are allowed for an element's role", + description: "Ensure ARIA attributes are allowed for an element's role", help: "Elements must only use allowed ARIA attributes", helpUrl: "https://dequeuniversity.com/rules/axe/3.5/aria-allowed-attr?application=axeAPI", ruleId: "aria-allowed-attr", @@ -182,7 +184,7 @@ In this example, we pass in the WCAG 2 A and AA tags into `axe.getRules` to retr To configure the format of the data used by axe. This can be used to add new rules, which must be registered with the library to execute. -**important**: `axe.configure()` does not communicate configuration calls into iframes. Instead `axe.configure()` must be called with the same argument in each `frame` / `iframe` individually. +**Important**: `axe.configure()` does not communicate configuration calls into iframes. Instead `axe.configure()` must be called with the same argument in each `frame` / `iframe` individually. #### Description @@ -213,7 +215,7 @@ axe.configure({ - `reporter` - Used to set the output format that the axe.run function will pass to the callback function. Can pass a reporter name or a custom reporter function. Valid names are: - `v1` to use the previous version's format: `axe.configure({ reporter: "v1" });` - `v2` to use the current version's format: `axe.configure({ reporter: "v2" });` - - `raw` to return the raw result data without formating: `axe.configure({ reporter: "raw" });` + - `raw` to return the raw result data without formatting: `axe.configure({ reporter: "raw" });` - `raw-env` to return the raw result data with environment data: `axe.configure({ reporter: "raw-env" });` - `no-passes` to return only violation results: `axe.configure({ reporter: "no-passes" });` - `checks` - Used to add checks to the list of checks used by rules, or to override the properties of existing checks @@ -567,7 +569,7 @@ The `resultTypes` option can be used to limit the number of nodes for a rule to After axe has processed all rules normally, it generates a unique selector for all nodes in all rules. This process can be time consuming, especially for pages with lots of nodes. By limiting the nodes to a maximum of one for result types you are not interested in, you can greatly speed up the tail end performance of axe. -Types listed in this option will cause rules that fall under those types to show all nodes. Types _not_ listed will causes rules that fall under one of the missing types to show a maximum of one node. This allows you to still see those results and inform the user of them if appropriate. +Types listed in this option will cause rules that fall under those types to show all nodes. Types _not_ listed will cause rules that fall under one of the missing types to show a maximum of one node. This allows you to still see those results and inform the user of them if appropriate. ```js axe.run( @@ -670,7 +672,7 @@ The results of axe are grouped according to their outcome into the following arr - `passes`: These results indicate what elements passed the rules - `violations`: These results indicate what elements failed the rules - `inapplicable`: These results indicate which rules did not run because no matching content was found on the page. For example, with no video, those rules won't run. -- `incomplete`: Also known as "needs review," these results were aborted and require further testing. This can happen either because of technical restrictions to what the rule can test, or because a javascript error occurred. +- `incomplete`: Also known as "needs review," these results were aborted and require further testing. This can happen either because of technical restrictions to what the rule can test, or because a JavaScript error occurred. Each object returned in these arrays have the following properties: @@ -710,7 +712,6 @@ axe.run(document, function (err, results) { - `passes[0]` ... - - `help` - `"Elements must have sufficient color contrast"` - `helpUrl` - `"https://dequeuniversity.com/courses/html-css/visual-layout/color-contrast"` - `id` - `"color-contrast"` @@ -723,7 +724,6 @@ axe.run(document, function (err, results) { ###### `violations` - `violations[0]` - - `help` - `"