diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 7b3aa4221..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,456 +0,0 @@ -version: 2.1 - -defaults: &defaults - working_directory: ~/axe-core - -unix_box: &unix_box - docker: - - image: cimg/node:18.18-browsers - -unix_nightly_box: &unix_nightly_box - docker: - - image: cimg/node:lts-browsers - -orbs: - puppeteer: threetreeslight/puppeteer@0.1.2 - browser-tools: circleci/browser-tools@1.4.8 - -set_npm_auth: &set_npm_auth - run: npm config set "//registry.npmjs.org/:_authToken" $NPM_AUTH - -restore_dependency_cache_unix: &restore_dependency_cache_unix - restore_cache: - name: Restore NPM Cache - keys: - - v9-cache-unix-{{ checksum "package-lock.json" }} - -restore_build: &restore_build - restore_cache: - name: Restore Axe.js Cache - keys: - - v9-cache-build-<< pipeline.git.revision >> - -commands: - browser-tools-job: - steps: - - browser-tools/install-browser-tools - -jobs: - # Fetch and cache dependencies. - dependencies_unix: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: - name: Skip Install If Cache Exists - command: | - if [ -d "node_modules" ]; then - echo "node_modules exist" - circleci step halt - else - echo "node_modules does not exist" - fi - - browser-tools-job - - <<: *set_npm_auth - - run: npm ci - - run: npx browser-driver-manager install chromedriver --verbose - - save_cache: - key: v9-cache-unix-{{ checksum "package-lock.json" }} - paths: - - node_modules - - # Build and cache axe.js - build_unix: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: npm run build - - save_cache: - key: v9-cache-build-<< pipeline.git.revision >> - paths: - - axe.js - - axe.min.js - - # Run ESLINT - lint: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: npm run eslint - - # Run the test suite. - test_chrome: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test -- --browsers Chrome - - run: npm run test:integration:chrome - - test_firefox: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - <<: *restore_build - - run: npm run test -- --browsers Firefox - - run: npm run test:integration:firefox - - # Run examples under `doc/examples` - test_examples: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:examples - - # Run ACT test cases - test_act: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:act - - # Run ARIA practices test cases - test_aria_practices: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:apg - - # Test locale files - test_locales: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:locales - - # Test virtual rules - test_virtual_rules: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:virtual-rules - - # Run the test suite for nightly builds. - test_nightly_browsers: - <<: *defaults - <<: *unix_nightly_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: sudo apt-get update -y - - <<: *restore_build - - run: - name: Install Chrome and ChromeDriver Beta - command: npx browser-driver-manager install chrome=beta chromedriver=beta --verbose - - 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 - - run: - name: Set Environment Variable - command: echo "export FIREFOX_NIGHTLY_BIN=$(pwd)/firefox/firefox-bin" >> $BASH_ENV - - run: npm run test -- --browsers Chrome,FirefoxNightly - - # Run the test suite for nightly builds. - test_nightly_act: - <<: *defaults - <<: *unix_nightly_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - browser-tools-job - # install ACT rules - # install first as for some reason installing a single package - # also re-installs all repo dependencies as well - - run: npm install w3c/wcag-act-rules#main - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:act - - # Run the test suite for nightly builds. - test_nightly_aria_practices: - <<: *defaults - <<: *unix_nightly_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - browser-tools-job - # install ARIA practices - # install first as for some reason installing a single package - # also re-installs all repo dependencies as well - - run: npm install w3c/aria-practices#main - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:apg - - # Test api docs can be built - build_api_docs: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: npm run api-docs - - # Test newest axe-core version rule help docs are active (only on - # master prs) - test_rule_help_version: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: npm run test:rule-help-version - - # Test jsdom API - test_jsdom: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - <<: *restore_build - - run: npm run test:jsdom - - # Release a "next" version - next_release: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *set_npm_auth - - <<: *restore_dependency_cache_unix - - <<: *restore_build - - run: npm run next-release - - run: .circleci/verify-release.sh - - run: npm publish --tag=next - - # Release a "production" version - verify_sri: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *set_npm_auth - - <<: *restore_dependency_cache_unix - - <<: *restore_build - - run: npm run sri-validate - - # Release a "production" version - release: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *set_npm_auth - - <<: *restore_dependency_cache_unix - - <<: *restore_build - - run: .circleci/verify-release.sh - - run: npm publish - - # Create a GitHub release. - github_release: - docker: - - image: cimg/go:1.17.1 - steps: - - checkout - - run: go get gopkg.in/aktau/github-release.v0 - - run: - name: Download and run GitHub release script - command: | - curl https://raw.githubusercontent.com/dequelabs/attest-release-scripts/develop/src/node-github-release.sh -s -o ./node-github-release.sh - chmod +x ./node-github-release.sh - ./node-github-release.sh - - # Verify released package has all required files - verify_release: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: .circleci/verify-release.sh post - - # Verify canary released package has all required files - verify_next_release: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: npm run next-release - - run: .circleci/verify-release.sh post - -workflows: - version: 2 - build: - jobs: - # install deps - - dependencies_unix - - build_unix: - requires: - - dependencies_unix - # Run linting - - lint: - requires: - - dependencies_unix - # Run tests on all commits, but after installing dependencies - - test_chrome: - requires: - - build_unix - - test_firefox: - requires: - - build_unix - - test_examples: - requires: - - build_unix - - test_act: - requires: - - build_unix - - test_aria_practices: - requires: - - build_unix - - test_locales: - requires: - - build_unix - - test_virtual_rules: - requires: - - build_unix - - build_api_docs: - requires: - - build_unix - - test_rule_help_version: - requires: - - build_unix - - test_jsdom: - requires: - - build_unix - # Verify the sri history is correct - - verify_sri: - requires: - - build_unix - filters: - branches: - only: - - /^release-.+/ - - master - # Hold for approval - - hold_release: - type: approval - requires: - - test_chrome - - test_firefox - - test_examples - - test_act - - test_aria_practices - - test_locales - - test_virtual_rules - - build_api_docs - - test_rule_help_version - - test_jsdom - - verify_sri - filters: - branches: - only: - - master - # Run a next release on "develop" commits, but only after the tests pass and dependencies are installed - - next_release: - requires: - - test_chrome - - test_firefox - - test_examples - - test_act - - test_aria_practices - - test_locales - - test_virtual_rules - - build_api_docs - - test_rule_help_version - - test_jsdom - filters: - branches: - only: develop - # Run a production release on "master" commits, but only after the tests pass and dependencies are installed - - release: - requires: - - hold_release - filters: - branches: - only: master - # Verify releases have all required files - - verify_release: - requires: - - release - filters: - branches: - only: master - - verify_next_release: - requires: - - next_release - filters: - branches: - only: develop - - github_release: - requires: - - release - nightly: - triggers: - - schedule: - # run at 00:00 UTC every day - cron: '0 0 * * *' - filters: - branches: - only: - - develop - jobs: - - dependencies_unix - - build_unix: - requires: - - dependencies_unix - - test_nightly_browsers: - requires: - - build_unix - - test_nightly_act: - requires: - - build_unix - - test_nightly_aria_practices: - requires: - - build_unix diff --git a/.circleci/verify-release.sh b/.circleci/verify-release.sh deleted file mode 100755 index 93a350f06..000000000 --- a/.circleci/verify-release.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash - -set -e - -# Verifying the release can fail due to race condition of -# npm not publishing the package before we try to install -# it -function wait_for_publish() { - echo "Installing $1@$2" - - set +e - for i in {1..10}; do - npm install "$1@$2" 2> /dev/null - if [ $? -eq 0 ]; then - echo "Successfully installed" - set -e - return - else - echo "Retrying..." - sleep 10 - fi - done - - echo "Unable to install. Exiting..." - exit 1 -} - -if [ -n "$1" ] && [ "$1" == "post" ] -then - # verify the released npm package in another dir as we can't - # install a package with the same name - version=$(node -pe "require('./package.json').version") - name=$(node -pe "require('./package.json').name") - - mkdir "verify-release-$version" - cd "verify-release-$version" - npm init -y - - wait_for_publish "$name" "$version" - - node -pe "window={}; document={}; require('$name')" - - cd "node_modules/${name}" -else - # verify main file exists - main=$(node -pe "require('./package.json').main") - node -pe "window={}; document={}; require('./$main')" -fi - -# Test if typescript file exists (if declared) -# -# Note: because we are using node to read the package.json, the -# variable gets set to the string `undefined` if the property -# does not exists, rather than an empty variable. -types=$(node -pe "require('./package.json').types") -if [ "$types" == "undefined" ] -then - types=$(node -pe "require('./package.json').typings") -fi - -if [ "$types" != "undefined" ] && [ ! -f "$types" ] -then - echo "types file missing" - exit 1; -fi \ No newline at end of file diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index ca1d987e9..000000000 --- a/.eslintignore +++ /dev/null @@ -1,11 +0,0 @@ -**/node_modules/* -**/tmp/* - -build/tasks/aria-supported.js - -doc/api/* -doc/examples/jest_react/*.js - -lib/core/imports/*.js -axe.js -axe.min.js \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 71a39bc1c..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,157 +0,0 @@ -module.exports = { - root: true, - extends: ['prettier'], - parserOptions: { - ecmaVersion: 2023 - }, - env: { - node: true, - es6: true - }, - globals: { - axe: true, - Promise: 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/actions/install-deps/action.yml b/.github/actions/install-deps/action.yml new file mode 100644 index 000000000..40c9aa43d --- /dev/null +++ b/.github/actions/install-deps/action.yml @@ -0,0 +1,79 @@ +name: 'Install Dependencies' +description: 'Install OS and Project dependencies' + +inputs: + node-version: + description: 'Node.js version to install' + required: false + start-xvfb: + description: 'If provided, this is the display number to run xvfb on. Should be in `:N` format, e.g., `:99`.' + required: false + nightly: + description: 'If true, installs the nightly versions of browsers.' + required: false +outputs: + chrome-path: + description: 'Path to the installed Chrome binary' + value: ${{ steps.setup-chrome.outputs.chrome-path }} + firefox-path: + description: 'Path to the installed Firefox binary' + value: ${{ steps.setup-firefox.outputs.firefox-path }} + chromedriver-path: + description: 'Path to the installed ChromeDriver binary' + value: ${{ steps.setup-chrome.outputs.chromedriver-path }} + chrome-version: + description: 'Version of the installed Chrome binary' + value: ${{ steps.setup-chrome.outputs.chrome-version }} + chromedriver-version: + description: 'Version of the installed ChromeDriver binary' + value: ${{ steps.setup-chrome.outputs.chromedriver-version }} + firefox-version: + description: 'Version of the installed Firefox binary' + value: ${{ steps.setup-firefox.outputs.firefox-version }} + +runs: + using: 'composite' + steps: + - name: Setup Node.js + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + registry-url: 'https://registry.npmjs.org' + node-version: ${{ inputs.node-version }} + node-version-file: ${{ inputs.node-version == '' && '.nvmrc' || '' }} + cache: npm + - name: Fix Chrome Sandbox Permissions + shell: bash + run: | + sudo chown root:root /opt/google/chrome/chrome-sandbox + sudo chmod 4755 /opt/google/chrome/chrome-sandbox + - name: Install Xvfb + shell: bash + if: ${{ inputs.start-xvfb }} + run: | + sudo apt-get update + sudo apt-get install -y xvfb x11-xserver-utils + - name: Install Google Chrome for Testing + id: setup-chrome + uses: browser-actions/setup-chrome@b94431e051d1c52dcbe9a7092a4f10f827795416 # v2.1.0 + with: + # @see https://github.com/dequelabs/axe-core/issues/5027 + chrome-version: ${{ inputs.nightly == 'true' && 'beta' || 145 }} + install-chromedriver: true + install-dependencies: true + - name: Install Firefox + id: setup-firefox + uses: browser-actions/setup-firefox@5914774dda97099441f02628f8d46411fcfbd208 # v1.7.0 + with: + firefox-version: ${{ inputs.nightly == 'true' && 'latest-nightly' || 'latest' }} + - name: Install Project Dependencies + shell: bash + run: npm ci + - name: Start Xvfb + if: ${{ inputs.start-xvfb }} + env: + DISPLAY: ${{ inputs.start-xvfb }} + shell: bash + # This is the same resolution as what CircleCI used. + # Maintaining it for consistency between the environments + # since something may be resolution dependent. + run: Xvfb "$DISPLAY" -screen 0 1280x1024x24 & diff --git a/.github/bin/determine-version.sh b/.github/bin/determine-version.sh new file mode 100755 index 000000000..076e7a77b --- /dev/null +++ b/.github/bin/determine-version.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -eo pipefail + +echo "::group::Determining new prerelease version" + +NAME=$(npm pkg get name | tr -d '"') +LATEST_VERSION=$(npm pkg get version | tr -d '"') + +SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) +NEW_VERSION="$LATEST_VERSION-canary.${SHORT_SHA}" + +echo "Latest version in package.json: $LATEST_VERSION" +echo "New prerelease version: $NEW_VERSION" + +echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT" +echo "name=$NAME" >> "$GITHUB_OUTPUT" + +echo "::endgroup::" diff --git a/.github/bin/extract-release-notes.sh b/.github/bin/extract-release-notes.sh new file mode 100755 index 000000000..409c89024 --- /dev/null +++ b/.github/bin/extract-release-notes.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +set -eu -o pipefail + +# Extracts the body of the most recent release section from CHANGELOG.md — +# everything between the latest version header and the previous one. The +# version header itself is not included. The result is written as a +# multiline `notes` step output to $GITHUB_OUTPUT when set (GitHub +# Actions), otherwise to stdout. +# +# The awk pattern /# \[?[0-9]/ matches version-header lines (`#`, space, +# optional `[`, digit), e.g. `### [4.11.4](...) (2026-04-23)`. The pattern +# is not anchored, so it matches the `# [4` substring inside `###`. On the +# first match it sets `found=1` and skips the line (so the header itself +# is not printed); on the next match (the previous release's header) it +# exits. `found{print}` prints every line in between. +# +# Walking through a typical CHANGELOG.md: +# +# Line v-header? found Action +# -------------------------------- --------- ------ ------- +# # Changelog no 0 nothing +# All notable changes... no 0 nothing +# ### [4.11.4](...) (2026-04-23) yes 0 -> 1 skip +# (blank) no 1 print +# ### Bug Fixes no 1 print +# - **commons/text:** ... no 1 print +# - **utils/getAncestry:** ... no 1 print +# ### [4.11.3](...) (2026-04-13) yes 1 exit + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" + +{ + echo 'notes<> "${GITHUB_OUTPUT:-/dev/stdout}" diff --git a/.github/bin/validate-npm-deploy.sh b/.github/bin/validate-npm-deploy.sh new file mode 100755 index 000000000..0e2029b5f --- /dev/null +++ b/.github/bin/validate-npm-deploy.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +set -eo pipefail + +if [ -z "$PACKAGE_NAME" ] || [ -z "$VERSION" ]; then + echo "::error::PACKAGE_NAME and VERSION environment variables must be set." + exit 1 +fi + +NPM_ROOT_PATH=$(npm root -g) + +npm install -g "${PACKAGE_NAME}@${VERSION}" || { + echo "::error::✗ Failed to install package: ${PACKAGE_NAME}@${VERSION}" + exit 1 +} + +cd "$NPM_ROOT_PATH" || { + echo "::error::✗ Failed to change directory to global npm root: $NPM_ROOT_PATH" + exit 1 +} + +node -pe "window={}; document={}; require('${PACKAGE_NAME}');" || { + echo "::error::✗ Failed to import CommonJS module for package: ${PACKAGE_NAME}" + exit 1 +} + +cd "${NPM_ROOT_PATH}/${PACKAGE_NAME}" || { + echo "::error::✗ Failed to change directory to package path: ${NPM_ROOT_PATH}/${PACKAGE_NAME}" + exit 1 +} + +types=$(node -pe "require('./package.json').types") +if [ "$types" == "undefined" ] +then + types=$(node -pe "require('./package.json').typings") +fi +if [ "$types" != "undefined" ] && [ ! -f "$types" ] +then + echo "::error::The types file is missing" + exit 1; +fi +echo "Types file '$types' is present in the package" diff --git a/.github/bin/validate-package.mjs b/.github/bin/validate-package.mjs new file mode 100755 index 000000000..c1126b131 --- /dev/null +++ b/.github/bin/validate-package.mjs @@ -0,0 +1,468 @@ +#!/usr/bin/env node + +/** + * @fileoverview Validates the package before publishing. + * This script performs several checks to ensure the package + * is correctly set up, including: + * - Verifying the existence of files listed in `package.json`'s `files` array. + * - Ensuring the package can be imported using both ESM `import` and CommonJS `require()`. + * - Validating Subresource Integrity (SRI) hashes for the built files. + * + * The script generates a summary report compatible with + * GitHub Actions, providing detailed feedback on each + * validation step. + * + * Running this script locally has a few implications to be + * aware of: + * 1. It links and unlinks the package globally. So this + * could impact other workspaces where current links are used. + * 2. To test the step summary, set the `GITHUB_STEP_SUMMARY` + * environment variable to a file path. If this file does not + * exist, it will be created. + */ + +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { createRequire } from 'node:module'; +import { access, appendFile, readFile } from 'node:fs/promises'; +import { execSync } from 'node:child_process'; +import pkg from '../../package.json' with { type: 'json' }; + +const isDebug = process.env.DEBUG === 'true'; +const repoRoot = resolve(import.meta.dirname, '..', '..'); +/** + * Start the exit code at 0 for a successful run. If any checks + * fail, we increment by 1 for each failure. When every check is done, + * we exit with the final exit code. + * + * This means our exit code informs us of how many failures happened. + * + * For anyone unfamiliar with exit codes in shell programs, + * an exit code of `0` means success, and any non-zero exit code + * means failure. + * + * Special note as well, in theory this _could_ go above `255`, + * causing the actual exit code to wrap around back to `0` and + * keep counting. But, if we have that many checks in here down + * the road then all the validation will need a major refactor. + */ +let exitCode = 0; +const missing = []; +const summaryFile = process.env.GITHUB_STEP_SUMMARY; +let summary = `# Package Validation + +
+
Package Name
+
${pkg.name}
+
Package Version
+
${pkg.version}
+
License
+
${pkg.license}
+
+ +`; + +console.group('Package Information'); +console.log('Name:', pkg.name); +console.log('Version:', pkg.version); +console.log('License:', pkg.license); +console.groupEnd(); + +/** + * Checks if a file or folder exists on the filesystem. + * + * @param {string} path - The path to check (relative to repo root) + * @returns {Promise} True if the path exists, false otherwise + */ +const exists = async path => { + const absolutePath = resolve(repoRoot, path); + try { + await access(absolutePath); + return true; + } catch { + return false; + } +}; + +/** + * Appends text to the GitHub Actions step summary file if it + * exists. This is mostly useful for local testing where the + * summary file may not be set. However if we want to set it + * for testing, we can. + * + * Since we build the summary in chunks, this function + * appends the current summary and then clears it for the + * next section. + * + * @param {string} text - The text to append to the summary file + * @returns {Promise} + */ +const appendToSummaryFile = async text => { + if (summaryFile) { + await appendFile(summaryFile, text); + summary = ''; + } +}; + +/** + * Verifies that all files and folders listed in the `files` + * array of `package.json` exist in the repository. + */ +const fileExistenceCheck = async () => { + summary += ` +\n## File Existence Check + +The following results table shows the status of files and folders +listed in the \`files\` array of \`package.json\`. + +> [!NOTE] +> This check only validates the existence of files and folders +> defined. It does not validate the contents. Thus a folder +> could exist but be empty and still pass this check. Or +> a file could exist but have incorrect syntax. + +| File | Status |\n|------|--------| +`; + + console.log('Checking for existence of package files:'); + + for (const file of pkg.files) { + if (await exists(file)) { + console.info(`✓ ${file}`); + summary += `| \`${file}\` | ✓ Found |\n`; + } else { + console.error(`✗ ${file}`); + summary += `| \`${file}\` | ✗ Missing |\n`; + missing.push(file); + } + } + + await appendToSummaryFile(summary); + + if (missing.length > 0) { + await appendToSummaryFile( + `\n**ERROR: Missing files: ${missing.join(', ')}**\n` + ); + console.error(`::error::Missing files: ${missing.join(', ')}`); + exitCode++; + } +}; + +/** + * Validates that the main package file can be loaded via + * CommonJS require. This ensures backward compatibility + * for projects using CommonJS. + */ +const validateCommonJS = async () => { + summary += `\n## CommonJS Compatibility Check + +This check validates that the main package file can be loaded +using CommonJS \`require()\`, ensuring backward compatibility. + +| File | Status | Version |\n|------|--------|--------| +`; + + const require = createRequire(import.meta.url); + + console.log('Validating CommonJS compatibility:'); + + try { + const axe = require(`${pkg.name}`); + + if (!axe || typeof axe !== 'object') { + throw new Error('Module did not export an object'); + } + + if (!axe.version) { + throw new Error('Missing version property'); + } + + console.info(`✓ ${pkg.name} (CommonJS)`); + summary += `| \`${pkg.name}\` | ✓ CommonJS Compatible | ${axe.version} |\n`; + } catch (error) { + console.error(`✗ ${pkg.name} (CommonJS):`, error.message); + summary += `| \`${pkg.name}\` | ✗ CommonJS Failed | Not Found |\n`; + summary += `\n\`\`\`\n${error.message}\n\`\`\`\n`; + exitCode++; + } + + await appendToSummaryFile(summary); +}; + +/** + * Validates that the package and all files listed in the + * `files` array of `package.json` can be imported using + * ESM `import` statements. + */ +const validateImportable = async () => { + summary += `\n## Importable Check + +This check attempts to import the package. As well as all +defined files in the \`files\` array of \`package.json\`. + +> [!NOTE] +> This check fails anything that resolves to \`node_modules\`, +> this is because \`axe-core\` should be linked before +> this is called. When \`exports\` can be added to the +> package definition, then we can self reference imports and +> the link will no longer be required. + +| File | Status | Version |\n|------|--------|--------| +`; + + // these files are not part of the importable api + const nonImportableFiles = ['gather-internals.js']; + const importTargets = [ + ...pkg.files + .filter(file => !nonImportableFiles.includes(file)) + .map(file => `${pkg.name}/${file}`) + ]; + let anyCaught = false; + + console.log('Validating package files are importable:'); + + try { + const axe = await import(pkg.name); + console.info(`✓ ${pkg.name}`); + + if (!axe.default?.version) { + throw new Error('Missing version property'); + } + + summary += `| \`${pkg.name}\` | ✓ Importable | ${axe.default.version} |\n`; + } catch { + console.error(`✗ ${pkg.name}`); + summary += `| \`${pkg.name}\` | ✗ Not Importable | Not Found |\n`; + anyCaught = true; + } + + for (const target of importTargets) { + // Skip things that can't be imported directly + // One day we can hopefully import anything as bytes to validate. + // Ref: https://github.com/tc39/proposal-import-bytes + if ( + target.endsWith('.txt') || + target.endsWith('/') || + target.endsWith('.d.ts') + ) { + continue; + } + + // `import.meta.resolve` is used here to determine + // where the import would be resolved from, following + // any symlinks. Since this package is linked, it + // should never have `node_modules` in the resolved + // path. It *could* happen if a dev is writing code + // within a parent folder named as such, but that is + // unsafe anyways. + // ------------------------------------------------- + // If this is ever setup to run in the post-deploy + // test, then this will cause issues as that runs + // from this folder specifically. + if (import.meta.resolve(target).includes('node_modules')) { + exitCode++; + summary += `| \`${target}\` | ✗ Resolves to node_modules |\n`; + console.error(`✗ ${target} resolves to node_modules`); + continue; + } + + try { + let version = ''; + if (target.endsWith('.json')) { + const data = await import(target, { with: { type: 'json' } }); + version = Object.keys(data.default).at(-1); + } else { + const axe = await import(target); + + if (!axe.default?.version) { + throw new Error('Missing version property'); + } + + version = axe.default.version; + } + console.info(`✓ ${target}`); + summary += `| \`${target}\` | ✓ Importable | ${version} |\n`; + } catch (error) { + console.error(`✗ ${target}`); + summary += `| \`${target}\` | ✗ Not Importable | Not Found |\n`; + summary += `\n\`\`\`\n${error.message}\n\`\`\`\n`; + anyCaught = true; + } + } + + // check that the non-importable files are parsable + for (const file of nonImportableFiles) { + const target = `${pkg.name}/${file}`; + try { + const resolved = import.meta.resolve(target); + if (resolved.includes('node_modules')) { + console.error(`✗ ${target} resolves to node_modules`); + summary += `| \`${target}\` | ✗ Resolves to node_modules |\n`; + exitCode++; + continue; + } + execSync(`node --check "${fileURLToPath(resolved)}"`, { stdio: 'pipe' }); + console.info(`✓ ${target} (parse-only, browser-only)`); + summary += `| \`${target}\` | ✓ Parse OK (browser-only) | n/a |\n`; + } catch (error) { + console.error(`✗ ${target}: ${error.message}`); + summary += `| \`${target}\` | ✗ Parse failed (browser-only) | n/a |\n`; + exitCode++; + } + } + + if (anyCaught) { + exitCode++; + } + + await appendToSummaryFile(summary); +}; + +/** + * When a PR targets `master` or a `release-*` branch, + * or these branches are pushed to, we run SRI validation. + * Otherwise, it is skipped since the SRI hashes are only + * updated when releasing. + * + * The history file is deprecated. However, until it is removed + * we should be prudent and continue to validate it. + */ +const validateSriHashes = async () => { + const currentBranch = + process.env.GITHUB_REF_NAME || process.env.GITHUB_HEAD_REF || ''; + + if (!/^release-.+/.test(currentBranch) && currentBranch !== 'master') { + console.log(`Skipping SRI validation (current branch: ${currentBranch})`); + return; + } + + summary += `\n## Subresource Integrity Check + +This check validates the current build against the SRI hash +for the version defined in \`sri-history.json\`. + +| File | Status | +|------|--------| +`; + + const sriHistory = await import(`${pkg.name}/sri-history.json`, { + with: { type: 'json' } + }); + const expectedSri = sriHistory.default[pkg.version]; + // calculate the SRI hash for `axe.js` and `axe.min.js` + // Using `sri-toolbox` as that is what is used in the build process + const { generate } = await import('sri-toolbox'); + + const filesToCheck = [ + { + name: 'axe.js', + path: fileURLToPath(import.meta.resolve(`${pkg.name}/axe.js`)) + }, + { + name: 'axe.min.js', + path: fileURLToPath(import.meta.resolve(`${pkg.name}/axe.min.js`)) + } + ]; + const mismatches = []; + + for (const file of filesToCheck) { + const calculatedSri = generate( + { algorithms: ['sha256'] }, + await readFile(file.path) + ); + + console.log(`Expected SRI for ${file.name}:`, expectedSri[file.name]); + console.log(`Calculated SRI for ${file.name}:`, calculatedSri); + if (calculatedSri !== expectedSri[file.name]) { + console.error(`✗ ${file.name}`); + summary += `| \`${file.name}\` | ✗ Invalid SRI |\n`; + mismatches.push({ + name: file.name, + expected: expectedSri[file.name], + calculated: calculatedSri + }); + continue; + } + + console.info(`✓ ${file.name}`); + summary += `| \`${file.name}\` | ✓ Valid SRI |\n`; + } + + if (mismatches.length > 0) { + summary += `\n### SRI Mismatches\n\n`; + + for (const mismatch of mismatches) { + summary += `**${mismatch.name}:**\n`; + summary += `- Expected: \`${mismatch.expected}\`\n`; + summary += `- Calculated: \`${mismatch.calculated}\`\n\n`; + } + + exitCode++; + } + + await appendToSummaryFile(summary); +}; + +// Start running checks that don't require linking first. +await fileExistenceCheck(); + +/** + * @type {import('child_process').ExecSyncOptionsWithBufferEncoding} + */ +const execOptions = { + cwd: repoRoot, + stdio: isDebug ? 'inherit' : 'pipe', + timeout: 200000 +}; + +console.log('Creating npm link for package validation...'); + +try { + // Link the package globally, then update the package + // internally to use the linked version. + // This is needed because we don't have `exports` defined + // yet, so self referencing imports won't work. + // We also have a circular dependency on the package. + // That means if we try to resolve the import without + // linking, it will resolve the version in `node_modules` + // from npm. + execSync('npm link', execOptions); + execSync(`npm link ${pkg.name}`, execOptions); + + // Run any checks that require the package to reference itself. + await validateCommonJS(); + await validateImportable(); + await validateSriHashes(); +} catch (error) { + console.error('Failed to create npm link:', error.message); + await appendToSummaryFile(` + ## Failed to create npm link + +
Click to expand error details + + \n\`\`\`\n${error.message}\n\`\`\`\n + +
+ + This failure prevented running critical validation checks. + Therefore the entire validation has failed. + `); + console.error(`Failed to create npm link: ${error.message}`); + exitCode++; +} + +console.log('Removing npm link...'); +try { + execSync(`npm unlink ${pkg.name}`, execOptions); + execSync('npm unlink -g', execOptions); +} catch (error) { + // Not a hard failure if unlinking fails since all these + // checks are last. As long as they completed fine, + // validation is acceptable. + // This is more for when running locally to test if + // something goes wrong. As the developer's machine state + // is impacted and they need to know about it. + console.error('Failed to remove npm link:', error.message); +} + +process.exit(exitCode); diff --git a/.github/bin/wait-for-npm-ready.sh b/.github/bin/wait-for-npm-ready.sh new file mode 100755 index 000000000..245f8abf7 --- /dev/null +++ b/.github/bin/wait-for-npm-ready.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +set -eo pipefail + +if [ -z "$VERSION" ] || [ -z "$PACKAGE_NAME" ]; then + echo "✗ ERROR: VERSION and PACKAGE_NAME environment variables must be set." + exit 1 +fi + +SLEEP_SECONDS=${SLEEP_SECONDS:-10} +MAX_ATTEMPTS=${MAX_ATTEMPTS:-30} + +echo "::group::Waiting for ${PACKAGE_NAME}@${VERSION} to be available on npm registry..." + +for i in $(seq 1 "$MAX_ATTEMPTS"); do + echo "Attempt $i of $MAX_ATTEMPTS..." + + if npm view "${PACKAGE_NAME}@${VERSION}" version > /dev/null 2>&1; then + PUBLISHED_VERSION=$(npm view "${PACKAGE_NAME}@${VERSION}" version) + echo "✓ Package ${PACKAGE_NAME}@${PUBLISHED_VERSION} is now available on npm!" + echo "::endgroup::" + exit 0 + fi + + if [ "$i" -lt "$MAX_ATTEMPTS" ]; then + echo "Package not yet available, waiting ${SLEEP_SECONDS} seconds..." + sleep "$SLEEP_SECONDS" + fi +done + +echo "✗ Timeout: Package ${PACKAGE_NAME}@${VERSION} not available after $((MAX_ATTEMPTS * SLEEP_SECONDS)) seconds" +echo "::endgroup::" + +exit 1 diff --git a/.github/bin/wait-for-workflow-success.sh b/.github/bin/wait-for-workflow-success.sh new file mode 100755 index 000000000..7ba2246e5 --- /dev/null +++ b/.github/bin/wait-for-workflow-success.sh @@ -0,0 +1,199 @@ +#!/usr/bin/env bash + +# This script waits for a specified GitHub Actions workflow to complete successfully. +# Debug mode can be enabled by setting the DEBUG environment variable to "true". +# Exit codes are as follows: +# 0 - Workflow completed successfully +# 1 - Workflow completed with failure +# 2 - Missing required tools on the host system +# 3 - Missing required environment variables for configuration +# 20 - Timeout waiting for workflow to complete + +set -eo pipefail + +if ! command -v jq &> /dev/null; then + echo "::error::jq is not installed. Please install jq to use this script." + exit 2 +fi + +if ! command -v gh &> /dev/null; then + echo "::error::GitHub CLI (gh) is not installed. Please install gh to use this script." + exit 2 +fi + +if [ -z "$REPOSITORY" ]; then + echo "::error::REPOSITORY environment variable must be set." + exit 3 +fi + +if [ -z "$SHA" ]; then + echo "::error::SHA environment variable must be set." + exit 3 +fi + +if [ -z "$WORKFLOW_NAME" ]; then + echo "::error::WORKFLOW_NAME environment variable must be set." + exit 3 +fi + +if [ -z "$BRANCH" ]; then + echo "::error::BRANCH environment variable must be set." + exit 3 +fi + +# When running locally for testing, this might be forgotten to get set. +# Create a temp file just so there is something to write to that will get thrown away. +if [ -z "$GITHUB_STEP_SUMMARY" ]; then + GITHUB_STEP_SUMMARY=$(mktemp) +fi + +echo "Waiting for '$WORKFLOW_NAME' workflow to complete for commit $SHA" + +# If not provided, default to 5 minutes for the job runner to time out. +TIMEOUT_MINUTES=${TIMEOUT_MINUTES:-5} +# Round down if given a fractional number by just removing the decimal portion. +TIMEOUT_MINUTES=${TIMEOUT_MINUTES%.*} +sleep_seconds=30 +max_attempts=$(( (TIMEOUT_MINUTES * 60) / sleep_seconds )) +attempt=0 + +# We *could* do `status=success` as a query parameter. But then we lose visibility +# into "in-progress" for debugging purposes to at least know if it found a run +# while waiting. +# Ref: https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#list-workflow-runs-for-a-repository +api_url="repos/$REPOSITORY/actions/runs?head_sha=$SHA&branch=$BRANCH&exclude_pull_requests=true&event=push" + +# This jq filter can seem complicated. So here is the breakdown: +# 1. `.workflow_runs` - Get the array of workflow runs from the API response +# 2. `sort_by(.created_at) | reverse` - Sort the runs by creation date in descending order. Since the API has no guaranteed order. +# 3. `[.[] | select(.name == "'"$WORKFLOW_NAME"'")][0]` - Filter the runs to only include those with the specified workflow name. Then take the first one (most recent) +# 4. `{status: .status, conclusion: .conclusion}` - Extract only the status and conclusion fields. Since we know this is the most recent run, we only care about these fields later. +# 5. `select(. != null)` - Ensure that we only get a result if there is a matching workflow run +jq_filter='.workflow_runs | sort_by(.created_at) | reverse | [.[] | select(.name == "'"$WORKFLOW_NAME"'")][0] | {status: .status, conclusion: .conclusion} | select(. != null)' + +cat >> "$GITHUB_STEP_SUMMARY" <> "$GITHUB_STEP_SUMMARY" +} + +while [ "$attempt" -lt "$max_attempts" ]; do + # Redirect errors to /dev/null to avoid unusable data in the variable in case of failure. + # If we seem to be having issues in CI later, it would be valuable to setup debugging to log to $GITHUB_STEP_SUMMARY. + workflow_data=$(gh api "$api_url" --jq "$jq_filter" 2>"$log_output" || echo "") + + if [ -z "$workflow_data" ]; then + echo "Attempt $((attempt + 1))/$max_attempts - Workflow run not found yet" + else + status=$(echo "$workflow_data" | jq -r '.status') + conclusion=$(echo "$workflow_data" | jq -r '.conclusion') + + echo "Attempt $((attempt + 1))/$max_attempts - Status: $status, Conclusion: $conclusion" + + if [ "$status" = "completed" ]; then + # Write the result to the summary file + function writeResultToSummary() { + cat >> "$GITHUB_STEP_SUMMARY" <> "$GITHUB_STEP_SUMMARY" < [!TIP] +> Re-running this workflow with debug mode enabled will capture API error logs to help diagnose issues. + +> [!WARNING] +> This can typically indicate that GitHub Action runners are experiencing delays. +> Please check the [GitHub Status Page](https://www.githubstatus.com/) for any ongoing incidents. +> If the status is normal, or if it already is, wait a little bit before re-running the workflow. + +> [!CAUTION] +> If another commit is already deployed, then do *not* re-run this deployment workflow. +> Re-running this would cause an older commit to be the next tag. +> If multiple deployments are failed in a row, then re-run them sequentially as the incident is resolved. + +EOF +writeLogToSummary + +echo "::error::Timeout waiting for '$WORKFLOW_NAME' workflow to complete" +exit 20 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b7ba1c567..c74cbed1f 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,31 @@ 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 in our tests + - dependency-name: 'glob' + versions: ['>=11.0.0'] + # Use node 4 types for backward compatibility + - dependency-name: '@types/node' + versions: ['>=5.0.0'] + # Breaking change that we need to handle in its own pr first + # @see https://github.com/dequelabs/axe-core/pull/4264 + - dependency-name: 'css-selector-parser' + versions: ['>=2.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/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000..f482037a1 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,202 @@ +# Do not rename this file. The name "deploy.yml" is known to +# npm for trusted OIDC publishing. +name: Deploy + +on: + # Run on push and not `workflow_run` after tests finish. + # Specifically because `workflow_run` only runs from the context + # of the default branch, regardless of which branch triggered the tests. + # That means no non-default branches could deploy. + push: + branches: + - master + - develop + +concurrency: + group: deploy/${{ github.ref_name }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + # Since we can't run against `workflow_run`, we have to + # wait for for the Tests to succeed first before any + # processing can happen. + wait-for-tests: + name: Wait for Tests to Pass + if: github.repository_owner == 'dequelabs' + runs-on: ubuntu-24.04 + permissions: + contents: read + actions: read + statuses: read + timeout-minutes: 15 + steps: + - &checkout + name: Checkout repository + timeout-minutes: 2 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false + - name: Wait for Tests workflow to complete + timeout-minutes: 13 + env: + SHA: ${{ github.sha }} + REPOSITORY: ${{ github.repository }} + BRANCH: ${{ github.ref_name }} + WORKFLOW_NAME: Tests + DEBUG: ${{ runner.debug == '1' }} + # One minute less than the job timeout to allow for the script to do cleanup work. + TIMEOUT_MINUTES: 12 + GH_TOKEN: ${{ github.token }} + run: ./.github/bin/wait-for-workflow-success.sh + deploy-next: + name: Deploy "next" to npm + needs: wait-for-tests + if: ${{ github.ref_name == 'develop' }} + environment: + name: registry.npmjs.org + permissions: + contents: read + id-token: write # Required for OIDC + runs-on: ubuntu-24.04 + outputs: + version: ${{ steps.determine-version.outputs.version }} + packageName: ${{ steps.determine-version.outputs.name }} + steps: + - *checkout + - &setup-node + name: Setup NodeJS + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + registry-url: 'https://registry.npmjs.org' + node-version-file: .nvmrc + cache: npm + - &install-project-deps + name: Install Project Dependencies + shell: bash + run: npm ci + - &build + name: Build + run: | + npm run prepare + npm run build + - name: Determine prerelease version + id: determine-version + run: ./.github/bin/determine-version.sh + - name: Bump version + env: + NEW_VERSION: ${{ steps.determine-version.outputs.version }} + run: npm version "$NEW_VERSION" --no-git-tag-version --ignore-scripts + - &validate-package + name: Validate package is consumable + env: + # Ref: https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#runner-context + # Linting shows this context might be invalid, but it shouldn't be per docs. + # Probably something missing in the schema. + DEBUG: ${{ runner.debug == '1' }} + run: node .github/bin/validate-package.mjs + - name: Publish "next" version to npm + run: npm publish --tag=next + validate-next-deploy: + name: Validate Next Deployment + needs: deploy-next + runs-on: ubuntu-24.04 + steps: + - *checkout + - *setup-node + # In theory since this is a new job now, by the time + # this would kick off the package should be available. + # But, to be safe in case of delays in propagation, + # we'll implement a retry mechanism. + - name: Wait for package to be available on npm + env: + VERSION: ${{ needs.deploy-next.outputs.version }} + PACKAGE_NAME: ${{ needs.deploy-next.outputs.packageName }} + run: ./.github/bin/wait-for-npm-ready.sh + - name: Validate installation of "next" version + env: + PACKAGE_NAME: ${{ needs.deploy-next.outputs.packageName }} + VERSION: ${{ needs.deploy-next.outputs.version }} + run: ./.github/bin/validate-npm-deploy.sh + prod-hold: + name: Await approval to deploy to production + needs: wait-for-tests + if: ${{ github.ref_name == 'master' }} + environment: + name: production-hold + runs-on: ubuntu-24.04 + steps: + - name: Awaiting approval to deploy to production + run: echo "Approval granted to proceed to production deployment." + prod-deploy: + name: Deploy stable to npm + needs: prod-hold + if: ${{ needs.prod-hold.result == 'success' }} + environment: + name: registry.npmjs.org + permissions: + contents: read + id-token: write # Required for OIDC + outputs: + version: ${{ steps.get-data.outputs.version }} + packageName: ${{ steps.get-data.outputs.name }} + runs-on: ubuntu-24.04 + steps: + - *checkout + - *setup-node + - *install-project-deps + - *build + - *validate-package + - name: Publish stable version to npm + run: npm publish + - name: Get published package data + id: get-data + run: | + VERSION=$(npm pkg get version | tr -d '"') + NAME=$(npm pkg get name | tr -d '"') + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "name=$NAME" >> $GITHUB_OUTPUT + create-github-release: + name: Create GitHub Release + needs: prod-deploy + runs-on: ubuntu-24.04 + permissions: + contents: write # Required to create releases + steps: + - *checkout + - name: Extract release notes + id: release-notes + run: ./.github/bin/extract-release-notes.sh + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + VERSION: ${{ needs.prod-deploy.outputs.version }} + NOTES: ${{ steps.release-notes.outputs.notes }} + run: | + gh release create "v${VERSION}" \ + --title "Release ${VERSION}" \ + --target ${{ github.sha }} \ + --notes "$NOTES" + validate-deploy: + name: Validate Deployment + needs: prod-deploy + runs-on: ubuntu-24.04 + steps: + - *checkout + - *setup-node + # In theory since this is a new job now, by the time + # this would kick off the package should be available. + # But, to be safe in case of delays in propagation, + # we'll implement a retry mechanism. + - name: Wait for package to be available on npm + env: + VERSION: ${{ needs.prod-deploy.outputs.version }} + PACKAGE_NAME: ${{ needs.prod-deploy.outputs.packageName }} + run: ./.github/bin/wait-for-npm-ready.sh + - name: Validate installation of stable version + env: + PACKAGE_NAME: ${{ needs.prod-deploy.outputs.packageName }} + VERSION: ${{ needs.prod-deploy.outputs.version }} + run: ./.github/bin/validate-npm-deploy.sh diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 74898ee2d..545beb920 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@v6 with: ref: ${{ github.event.pull_request.head.ref }} - name: Install dependencies run: npm ci - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 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@04702edda442b2e678b25b537cec683a1493fcb9 # tag=v5 with: commit_message: ':robot: Automated formatting fixes' diff --git a/.github/workflows/nightly-tests.yml b/.github/workflows/nightly-tests.yml new file mode 100644 index 000000000..8ea4beaad --- /dev/null +++ b/.github/workflows/nightly-tests.yml @@ -0,0 +1,83 @@ +name: Nightly Tests + +on: + schedule: + # Runs every day at 2:17 AM UTC + # Schedules should try to be offset from common times + # to avoid high contention times on GitHub runners. + - cron: '17 2 * * *' + workflow_dispatch: + +env: + CHROME_DEVEL_SANDBOX: /opt/google/chrome/chrome-sandbox + +permissions: + contents: read + +jobs: + browsers: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + env: + DISPLAY: ':99' + steps: + - &checkout + name: Checkout repository + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false + - name: Install Dependencies + id: install-deps + uses: ./.github/actions/install-deps + with: + nightly: 'true' + start-xvfb: ${{ env.DISPLAY }} + - &build + name: Build + id: build + run: | + npm run prepare + npm run build + - name: Run Firefox Nightly Browser Tests + env: + WTR_BROWSER: firefox-nightly + FIREFOX_NIGHTLY_BIN: ${{ steps.install-deps.outputs.firefox-path }} + run: npm run test + - name: Run Chrome Beta Browser Tests + if: ${{ !cancelled() && steps.build.conclusion == 'success' }} + env: + WTR_BROWSER: chrome + CHROME_BIN: ${{ steps.install-deps.outputs.chrome-path }} + CHROMEDRIVER_BIN: ${{ steps.install-deps.outputs.chromedriver-path }} + run: npm run test + act: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - *checkout + - &install-deps + name: Install Deps + id: install-deps + uses: ./.github/actions/install-deps + - *build + - name: Install Latest WCAG ACT Rules + run: npm install w3c/wcag-act-rules#main + - name: Run ACT Tests + env: + CHROME_BIN: ${{ steps.install-deps.outputs.chrome-path }} + CHROMEDRIVER_BIN: ${{ steps.install-deps.outputs.chromedriver-path }} + run: npm run test:act + aria-practices: + runs-on: ubuntu-24.04 + timeout-minutes: 7 + steps: + - *checkout + - *install-deps + - *build + - name: Install Latest W3C Aria Practices + run: npm install w3c/aria-practices#main + - name: Run ARIA Practices Tests + env: + CHROME_BIN: ${{ steps.install-deps.outputs.chrome-path }} + CHROMEDRIVER_BIN: ${{ steps.install-deps.outputs.chromedriver-path }} + run: npm run test:apg diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ba0714f1c..ad707b7f5 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@v6 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 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 5c5e1cdb2..58f5be08f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,40 +6,252 @@ on: branches: - master - develop + - release-* + +# We want to group to the workflow for each branch. +# Non-push events will be cancelled if a new one is started. +# Push events will run sequentially. This helps ensure that +# the `next` tag isn't out of sync. +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: {} + +env: + CHROME_DEVEL_SANDBOX: /opt/google/chrome/chrome-sandbox jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 5 + lint: + runs-on: ubuntu-24.04 + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - &checkout + name: Checkout repository + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false + - &setup-node + name: Set up Node.js + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 20 + node-version-file: .nvmrc cache: 'npm' - - run: npm ci - - run: npm run build - # v4 download seems to have some flakiness with the download of artifacts so pinning to v3 for now - # @see https://github.com/actions/download-artifact/issues/249 - - uses: actions/upload-artifact@v3 + - &install-deps-directly + name: Install Dependencies + run: npm ci + - name: Run ESLint + run: npm run eslint + + fmt_check: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - *checkout + - *setup-node + - *install-deps-directly + - run: npm run fmt:check + + build: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - *checkout + - *setup-node + - *install-deps-directly + - &build + name: Build + run: | + npm run prepare + npm run build + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: axe-core path: axe.js retention-days: 1 + test_chrome: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + env: + DISPLAY: :99 + steps: + - *checkout + - &install-deps-with-xvfb + name: Install Deps + uses: ./.github/actions/install-deps + id: install-deps + with: + start-xvfb: ${{ env.DISPLAY }} + - *build + - name: Run Tests Against Chrome + env: + WTR_BROWSER: chrome + CHROME_BIN: ${{ steps.install-deps.outputs.chrome-path }} + CHROMEDRIVER_BIN: ${{ steps.install-deps.outputs.chromedriver-path }} + run: npm run test + - name: Run Chrome Integration Tests + env: + CHROME_BIN: ${{ steps.install-deps.outputs.chrome-path }} + CHROMEDRIVER_BIN: ${{ steps.install-deps.outputs.chromedriver-path }} + run: npm run test:integration:chrome + + test_firefox: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + env: + DISPLAY: :99 + steps: + - *checkout + - *install-deps-with-xvfb + - *build + - name: Run Tests Against Firefox + env: + WTR_BROWSER: firefox + FIREFOX_BIN: ${{ steps.install-deps.outputs.firefox-path }} + run: npm run test + - name: Run Firefox Integration Tests + env: + FIREFOX_BIN: ${{ steps.install-deps.outputs.firefox-path }} + run: npm run test:integration:firefox + + # Run examples under `doc/examples` + test_examples: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + # Temporarily non-blocking due to an upstream issue with the examples. + # A failure here will not fail the Tests workflow, so it won't block deploys. + # Remove this once the upstream issue is resolved. + continue-on-error: true + steps: + - *checkout + - &install-deps + name: Install Deps + id: install-deps + uses: ./.github/actions/install-deps + - *build + - name: Run Tests Against Examples + env: + PUPPETEER_EXECUTABLE_PATH: ${{ steps.install-deps.outputs.chrome-path }} + run: npm run test:examples + + test_act: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + needs: build + steps: + - *checkout + - *install-deps + - &restore-axe-build + name: Restore axe build + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: axe-core + - name: Run ACT Tests + env: + CHROME_BIN: ${{ steps.install-deps.outputs.chrome-path }} + CHROMEDRIVER_BIN: ${{ steps.install-deps.outputs.chromedriver-path }} + run: npm run test:act + + test_aria_practices: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + needs: build + steps: + - *checkout + - *install-deps + - *restore-axe-build + - name: Run ARIA Practices Tests + env: + CHROME_BIN: ${{ steps.install-deps.outputs.chrome-path }} + CHROMEDRIVER_BIN: ${{ steps.install-deps.outputs.chromedriver-path }} + run: npm run test:apg + + test_locales: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + needs: build + steps: + - *checkout + - *install-deps + - *restore-axe-build + - name: Run Locale Tests + run: npm run test:locales + + test_virtual_rules: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + needs: build + steps: + - *checkout + - *install-deps + - *restore-axe-build + - name: Run Virtual Rules Tests + run: npm run test:virtual-rules + + test_jsdom: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + needs: build + steps: + - *checkout + - *install-deps + - *restore-axe-build + - name: Run jsdom Tests + run: npm run test:jsdom + + build_api_docs: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - *checkout + - *install-deps + - name: Run API Docs Build + run: npm run api-docs + + test_rule_help_version: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + if: ${{ github.ref_name == 'master' }} + steps: + - *checkout + - *install-deps + - name: Run Rule Help Version Tests + run: npm run test:rule-help-version + + sri-validate: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + needs: build + # Run on master and RC branches along with PRs targeting those branches. + if: ${{ github.ref_name == 'master' || startsWith(github.ref_name, 'release-') || github.event.pull_request.base.ref == 'master' || startsWith(github.event.pull_request.base.ref, 'release-') }} + steps: + - *checkout + - *install-deps + - *restore-axe-build + - name: Validate Subresource Integrity + run: npm run sri-validate + test_node: + # The package can't be built on Node 6 anymore, but should still run there. + # So we need to pull in a previous build artifact. + needs: build strategy: matrix: - node: [6, 18, 20] - runs-on: ubuntu-latest - timeout-minutes: 5 - needs: build + node: + - 6 + - 18 + - 20 + - 22 + - 24 + runs-on: ubuntu-24.04 + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - *checkout + - name: Set up Node.js + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: ${{ matrix.node}} - - uses: actions/download-artifact@v3 - with: - name: axe-core - - run: npm run test:node + node-version: ${{ matrix.node }} + - *restore-axe-build + - name: Run Node.js Tests + run: npm run test:node diff --git a/.github/workflows/update-generated-files.yaml b/.github/workflows/update-generated-files.yaml index 2277e82c6..6ac667002 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@v6 with: fetch-depth: 0 + - uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: 'npm' - name: Build run: | @@ -25,10 +29,10 @@ jobs: - name: Check for changes id: changes run: | - changes=$(git status --porcelain) + changes=$(git status --porcelain | tr -d '\n') # 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 fe79e6d26..0eb44dfdf 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ test/integration/*/index.html # dist axe.js axe.*.js +/gather-internals.js # generated src file lib/core/base/metadata-function-map.js @@ -28,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..a45fd52cc --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..25c4925c7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "attach", + "name": "Attach to WTR test:debug", + "address": "localhost", + "port": 9765, // keep in sync with debugPort in wtr.config.mjs chrome-debug group + "webRoot": "${workspaceFolder}", + "sourceMaps": true, + "sourceMapPathOverrides": { + "*": "${webRoot}/*" + } + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 3523aebf7..9d577f100 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,196 @@ 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. +### [4.12.1](https://github.com/dequelabs/axe-core/compare/v4.12.0...v4.12.1) (2026-06-09) + +### Bug Fixes + +- **axe.d.ts:** make enabled property of RuleMetadata optional ([#5129](https://github.com/dequelabs/axe-core/issues/5129)) ([7eb3d2d](https://github.com/dequelabs/axe-core/commit/7eb3d2d7ce283926a786c8a0fbf2a52c81d5e694)) + +## [4.12.0](https://github.com/dequelabs/axe-core/compare/v4.11.4...v4.12.0) (2026-06-01) + +### Features + +- add gather-internals.js external script ([#5099](https://github.com/dequelabs/axe-core/issues/5099)) ([c61d58b](https://github.com/dequelabs/axe-core/commit/c61d58b40d87f81152526edcea67292aa7e3ae1d)), closes [#5080](https://github.com/dequelabs/axe-core/issues/5080) +- **aria-allowed/prohibited-attr, aria-required-parent/children:** partially support element internals role ([#5080](https://github.com/dequelabs/axe-core/issues/5080)) ([417b48a](https://github.com/dequelabs/axe-core/commit/417b48a0d60f0c01ce81e69cc50c2c59e45aa4de)), closes [#5039](https://github.com/dequelabs/axe-core/issues/5039) [#4259](https://github.com/dequelabs/axe-core/issues/4259) +- **axe.externalAPIs:** add public api for setting elementInternal data ([#5105](https://github.com/dequelabs/axe-core/issues/5105)) ([63bab8f](https://github.com/dequelabs/axe-core/commit/63bab8fec82817849a8e69b7cd00f1c1bf3ddf6e)) +- **core:** expose normalizeRunOptions ([#4998](https://github.com/dequelabs/axe-core/issues/4998)) ([b8e6a59](https://github.com/dequelabs/axe-core/commit/b8e6a5943f3d7613e770f36dd15fdb27621ca18c)) +- expose axe.resetLocale() to restore the default locale ([#5108](https://github.com/dequelabs/axe-core/issues/5108)) ([c2b5292](https://github.com/dequelabs/axe-core/commit/c2b5292397727e1f9d63ae1675db447a5cf58a23)), closes [#5107](https://github.com/dequelabs/axe-core/issues/5107) +- **getRules:** include rule enabled state in returned objects ([#5118](https://github.com/dequelabs/axe-core/issues/5118)) ([75bf772](https://github.com/dequelabs/axe-core/commit/75bf772d47ec1cc6027de55b47aaa63ffef171da)), closes [#5116](https://github.com/dequelabs/axe-core/issues/5116) +- **list,listitem:** support element internals role ([#5119](https://github.com/dequelabs/axe-core/issues/5119)) ([7d9d696](https://github.com/dequelabs/axe-core/commit/7d9d69678df257ee72b962d45371ae27e3aa82ca)) +- **new-rule:** check that aria-tab have an accessible name ([#5001](https://github.com/dequelabs/axe-core/issues/5001)) ([0d4e4e7](https://github.com/dequelabs/axe-core/commit/0d4e4e70aa9f46519eb6000744e043c058fd994e)), closes [#4842](https://github.com/dequelabs/axe-core/issues/4842) +- **rules:** deprecate landmark-complementary-is-top-level rules ([#4992](https://github.com/dequelabs/axe-core/issues/4992)) ([9e09139](https://github.com/dequelabs/axe-core/commit/9e091391189dba452ea485275609120e1e6ae8ba)), closes [#4950](https://github.com/dequelabs/axe-core/issues/4950) +- **utils:** add `getElementInternals` function ([#5077](https://github.com/dequelabs/axe-core/issues/5077)) ([1c15f82](https://github.com/dequelabs/axe-core/commit/1c15f8224a184d3c0da95942e99d9d73ad5645c0)) + +### Bug Fixes + +- **aria-allowed-attr:** restrict br and wbr elements to aria-hidden only ([#4974](https://github.com/dequelabs/axe-core/issues/4974)) ([c6245e7](https://github.com/dequelabs/axe-core/commit/c6245e7aee824434fcdae3c77c24365493dbe4be)) +- **aria-conditional-attr:** add support for radio ([#5100](https://github.com/dequelabs/axe-core/issues/5100)) ([8223c98](https://github.com/dequelabs/axe-core/commit/8223c989ff4fd2b8002f4961a8ee005a371f39cc)) +- **aria-valid-attr-value:** handle multiple aria-errormessage IDs ([#4973](https://github.com/dequelabs/axe-core/issues/4973)) ([0489e30](https://github.com/dequelabs/axe-core/commit/0489e30aad3d80790d8fb9cf5b1807d7c3a2179f)) +- **aria:** prevent getOwnedVirtual from returning duplicate nodes ([#4987](https://github.com/dequelabs/axe-core/issues/4987)) ([48ca955](https://github.com/dequelabs/axe-core/commit/48ca9554e2f0400caeec55c09aa100cbb415422d)), closes [#4840](https://github.com/dequelabs/axe-core/issues/4840) +- **commons/text:** exclude natively hidden elements from aria-labelledby accessible name ([#5076](https://github.com/dequelabs/axe-core/issues/5076)) ([ea7202c](https://github.com/dequelabs/axe-core/commit/ea7202c6bf1a6166c878dbf19bb5454372b61fae)), closes [#4704](https://github.com/dequelabs/axe-core/issues/4704) +- **DqElement:** avoid calling constructors with cloneNode ([#5013](https://github.com/dequelabs/axe-core/issues/5013)) ([0281fa1](https://github.com/dequelabs/axe-core/commit/0281fa16f7110b793ac8b3b5b46f93e81be75ee4)) +- **existing-rule:** aria-busy now shows an error message for a use with unallowed children ([#5017](https://github.com/dequelabs/axe-core/issues/5017)) ([2067b87](https://github.com/dequelabs/axe-core/commit/2067b87195552daa3065be7aca1aa2a02c135f28)) +- **helpUrl:** ensure axe.configure always updates the help URLs ([#5114](https://github.com/dequelabs/axe-core/issues/5114)) ([c4f60ff](https://github.com/dequelabs/axe-core/commit/c4f60ffcd47eb64514e8cbafbc68ad357ce60e77)) +- **label-content-name-mismatch:** match visible text with aria-label and exclude invisible text ([#5096](https://github.com/dequelabs/axe-core/issues/5096)) ([3a012a1](https://github.com/dequelabs/axe-core/commit/3a012a141f56b76d6a58fcfb01598ba45e91a442)) +- **locale:** ensure all subtags are correctly set ([#5112](https://github.com/dequelabs/axe-core/issues/5112)) ([13005ed](https://github.com/dequelabs/axe-core/commit/13005eda098db154f3d78df3923ed85389344353)) +- **scrollable-region-focusable:** clarify the issue is in safari ([#4995](https://github.com/dequelabs/axe-core/issues/4995)) ([4ec5211](https://github.com/dequelabs/axe-core/commit/4ec52112b67b1b44f82b3eade1825789ee8cb659)), closes [WebKit#190870](https://github.com/dequelabs/WebKit/issues/190870) [WebKit#277290](https://github.com/dequelabs/WebKit/issues/277290) +- **scrollable-region-focusable:** do not fail scroll areas when all content is visible without scrolling ([#4993](https://github.com/dequelabs/axe-core/issues/4993)) ([838707a](https://github.com/dequelabs/axe-core/commit/838707a8f224907042221bbf6fb28d6ad59d7cb0)) +- **target-size:** determine offset using clientRects if target is display:inline ([#5012](https://github.com/dequelabs/axe-core/issues/5012)) ([a4b8091](https://github.com/dequelabs/axe-core/commit/a4b809183f43c4296a3ec57cd80d8a8f34743361)) +- **target-size:** ignore position: fixed elements that are offscreen when page is scrolled ([#5066](https://github.com/dequelabs/axe-core/issues/5066)) ([1229a6e](https://github.com/dequelabs/axe-core/commit/1229a6e7162768a283f5e2307024dee0d0566452)), closes [#5065](https://github.com/dequelabs/axe-core/issues/5065) +- **target-size:** ignore widgets that are inline with other inline elements ([#5000](https://github.com/dequelabs/axe-core/issues/5000)) ([a8dd81b](https://github.com/dequelabs/axe-core/commit/a8dd81be759c670203784acf7b1894257df5457c)) +- **utils/getAncestry:** escape node name ([#5079](https://github.com/dequelabs/axe-core/issues/5079)) ([d1fabaa](https://github.com/dequelabs/axe-core/commit/d1fabaad99b1b055b2436a0c3efc22cb66df3934)), closes [#5078](https://github.com/dequelabs/axe-core/issues/5078) +- **utils:** Add null check to parseCrossOriginStylesheet, closes [#5074](https://github.com/dequelabs/axe-core/issues/5074) ([#5075](https://github.com/dequelabs/axe-core/issues/5075)) ([f12ef32](https://github.com/dequelabs/axe-core/commit/f12ef32554deb116238ac29d854ad8e46baa9adb)) +- **utils:** update isShadowRoot to use spec-compliant custom element regex ([#5059](https://github.com/dequelabs/axe-core/issues/5059)) ([edc6ce2](https://github.com/dequelabs/axe-core/commit/edc6ce2815b79a976bdb654bd8062f28132a3cdd)), closes [#5030](https://github.com/dequelabs/axe-core/issues/5030) + +### [4.11.4](https://github.com/dequelabs/axe-core/compare/v4.11.3...v4.11.4) (2026-04-23) + +### Bug Fixes + +- **commons/text:** exclude natively hidden elements from aria-labelledby accessible name ([#5076](https://github.com/dequelabs/axe-core/issues/5076)) ([df34adf](https://github.com/dequelabs/axe-core/commit/df34adfc1967919d667d40a76ab5c85b6e47ddfe)), closes [#4704](https://github.com/dequelabs/axe-core/issues/4704) +- **utils/getAncestry:** escape node name ([#5079](https://github.com/dequelabs/axe-core/issues/5079)) ([6e68d0a](https://github.com/dequelabs/axe-core/commit/6e68d0a5d26999b996152df82238bc3f3a041cb3)), closes [#5078](https://github.com/dequelabs/axe-core/issues/5078) + +### [4.11.3](https://github.com/dequelabs/axe-core/compare/v4.11.2...v4.11.3) (2026-04-13) + +### Bug Fixes + +- **aria-allowed-attr:** restrict br and wbr elements to aria-hidden only ([#4974](https://github.com/dequelabs/axe-core/issues/4974)) ([1d80163](https://github.com/dequelabs/axe-core/commit/1d801636f058f2abd885c488baff954872b13846)) +- **target-size:** ignore position: fixed elements that are offscreen when page is scrolled ([#5066](https://github.com/dequelabs/axe-core/issues/5066)) ([5906273](https://github.com/dequelabs/axe-core/commit/5906273841cbd7ac9e08af730dffc244cf42b39b)), closes [#5065](https://github.com/dequelabs/axe-core/issues/5065) + +### [4.11.2](https://github.com/dequelabs/axe-core/compare/v4.11.1...v4.11.2) (2026-03-30) + +### Bug Fixes + +- **aria-valid-attr-value:** handle multiple aria-errormessage IDs ([#4973](https://github.com/dequelabs/axe-core/issues/4973)) ([9322148](https://github.com/dequelabs/axe-core/commit/9322148b69924d6ae2d2dd46e6109bc2fc53abd3)) +- **aria:** prevent getOwnedVirtual from returning duplicate nodes ([#4987](https://github.com/dequelabs/axe-core/issues/4987)) ([99d1e77](https://github.com/dequelabs/axe-core/commit/99d1e77f351db586c79020372efc59608e604a1c)), closes [#4840](https://github.com/dequelabs/axe-core/issues/4840) +- **DqElement:** avoid calling constructors with cloneNode ([#5013](https://github.com/dequelabs/axe-core/issues/5013)) ([88bc57f](https://github.com/dequelabs/axe-core/commit/88bc57fd3de12cd69d365b3f385ce9a2e30b7bd5)) +- **existing-rule:** aria-busy now shows an error message for a use with unallowed children ([#5017](https://github.com/dequelabs/axe-core/issues/5017)) ([dded75a](https://github.com/dequelabs/axe-core/commit/dded75a9acc9a2350926f46f04e0f1de522f43d6)) +- **scrollable-region-focusable:** clarify the issue is in safari ([#4995](https://github.com/dequelabs/axe-core/issues/4995)) ([2567afd](https://github.com/dequelabs/axe-core/commit/2567afd5c32398c6a488240b066bb0d335f6dc6a)), closes [WebKit#190870](https://github.com/dequelabs/WebKit/issues/190870) [WebKit#277290](https://github.com/dequelabs/WebKit/issues/277290) +- **scrollable-region-focusable:** do not fail scroll areas when all content is visible without scrolling ([#4993](https://github.com/dequelabs/axe-core/issues/4993)) ([240f8b5](https://github.com/dequelabs/axe-core/commit/240f8b53ad168521a63b54d0053b96ce430c7184)) +- **target-size:** determine offset using clientRects if target is display:inline ([#5012](https://github.com/dequelabs/axe-core/issues/5012)) ([69d81c1](https://github.com/dequelabs/axe-core/commit/69d81c1cbb6a61a272884516c7983dcd17d28a42)) +- **target-size:** ignore widgets that are inline with other inline elements ([#5000](https://github.com/dequelabs/axe-core/issues/5000)) ([cf8a3c0](https://github.com/dequelabs/axe-core/commit/cf8a3c039b121c0c64a1eb8805c586659b69f3c1)) + +### [4.11.1](https://github.com/dequelabs/axe-core/compare/v4.11.0...v4.11.1) (2026-01-06) + +### Bug Fixes + +- allow shadow roots in axe.run contexts ([#4952](https://github.com/dequelabs/axe-core/issues/4952)) ([d4aee16](https://github.com/dequelabs/axe-core/commit/d4aee16494f3225e9f5065f23a9e1deccb46fc9a)), closes [#4941](https://github.com/dequelabs/axe-core/issues/4941) +- color contrast fails for oklch and oklab with none ([#4959](https://github.com/dequelabs/axe-core/issues/4959)) ([8f249fd](https://github.com/dequelabs/axe-core/commit/8f249fdcffe379466fcff8ec8ac46e37b65fdbce)) +- **color-contrast:** do not incomplete on textarea ([#4968](https://github.com/dequelabs/axe-core/issues/4968)) ([d271788](https://github.com/dequelabs/axe-core/commit/d27178866d4962e1157b1be435143d028873f545)), closes [#4947](https://github.com/dequelabs/axe-core/issues/4947) +- **commons/color:** Match browser behavior for out-of-gamut oklch colors ([#4908](https://github.com/dequelabs/axe-core/issues/4908)) ([5036be8](https://github.com/dequelabs/axe-core/commit/5036be811e0ede4bf061ab1f970f78b7e9c7ec0c)) +- don't runs rules that select `html` on nested `html` elements ([#4969](https://github.com/dequelabs/axe-core/issues/4969)) ([1e9a5c3](https://github.com/dequelabs/axe-core/commit/1e9a5c36812ff69a75f23fed3d290497f9fba37d)) +- replaced luminance threshold constant 0.03928 with 0.04045 ([#4934](https://github.com/dequelabs/axe-core/issues/4934)) ([316967d](https://github.com/dequelabs/axe-core/commit/316967d50c554e71bcdf59ac945d1d5bb2f0684b)), closes [#4933](https://github.com/dequelabs/axe-core/issues/4933) +- **rgaa:** adjust mapping of aria-hidden-\* and valid-lang ([#4935](https://github.com/dequelabs/axe-core/issues/4935)) ([77571f2](https://github.com/dequelabs/axe-core/commit/77571f2103a90a5703233729c78be008395f1572)) +- **valid-lang:** update valid-langs for newer language codes ([#4966](https://github.com/dequelabs/axe-core/issues/4966)) ([c3f5446](https://github.com/dequelabs/axe-core/commit/c3f54464fd0995edc6619203b46b65d2984b218d)), closes [#4963](https://github.com/dequelabs/axe-core/issues/4963) + +## [4.11.0](https://github.com/dequelabs/axe-core/compare/v4.10.3...v4.11.0) (2025-10-07) + +### Features + +- add RGAA tags to rules ([#4862](https://github.com/dequelabs/axe-core/issues/4862)) ([53a925a](https://github.com/dequelabs/axe-core/commit/53a925a31bb2bf4a1584252fa7a58c0662225320)) +- **aria-prohibited-attr:** add support for fallback roles ([#4325](https://github.com/dequelabs/axe-core/issues/4325)) ([62a19a9](https://github.com/dequelabs/axe-core/commit/62a19a9f753f8c49885dafbab7a2a9468eb6571d)) +- **axe.d.ts:** add nodeSerializer typings ([#4551](https://github.com/dequelabs/axe-core/issues/4551)) ([a2f3a48](https://github.com/dequelabs/axe-core/commit/a2f3a485d5e02993c0f35762cd9d80a6ce4ced5f)), closes [#4093](https://github.com/dequelabs/axe-core/issues/4093) +- **DqElement:** deprecate fromFrame function ([#4881](https://github.com/dequelabs/axe-core/issues/4881)) ([374c376](https://github.com/dequelabs/axe-core/commit/374c376d0b4a043e8beaa7cc9a47521314eee02c)), closes [#4093](https://github.com/dequelabs/axe-core/issues/4093) +- **DqElement:** Truncate large `html` strings when the element has a large outerHTML string ([#4796](https://github.com/dequelabs/axe-core/issues/4796)) ([404a4fb](https://github.com/dequelabs/axe-core/commit/404a4fb24a156dc433ac9c70dbefe415d41ca980)), closes [#4544](https://github.com/dequelabs/axe-core/issues/4544) +- **get-xpath:** return proper relative selector for id ([#4846](https://github.com/dequelabs/axe-core/issues/4846)) ([1035f9e](https://github.com/dequelabs/axe-core/commit/1035f9ef134cbfc02c91c37f881eb5759f602bf3)), closes [#4845](https://github.com/dequelabs/axe-core/issues/4845) +- **i18n:** Add Portugal Portuguese translation ([#4725](https://github.com/dequelabs/axe-core/issues/4725)) ([5b6a65a](https://github.com/dequelabs/axe-core/commit/5b6a65a103188251568862f46020488cf7fd8a94)) +- incomplete with node on which an error occurred ([#4863](https://github.com/dequelabs/axe-core/issues/4863)) ([32ed8da](https://github.com/dequelabs/axe-core/commit/32ed8daad1111772559f6e1cf6c8171e83c0f299)) +- **locale:** Added ru locale ([#4565](https://github.com/dequelabs/axe-core/issues/4565)) ([067b01d](https://github.com/dequelabs/axe-core/commit/067b01d66db1d2c276f26743a8d13d5d60d33446)) +- **tap:** some best practice rules map to RGAA ([#4895](https://github.com/dequelabs/axe-core/issues/4895)) ([bc33f4c](https://github.com/dequelabs/axe-core/commit/bc33f4cf5d4d384118c08d8be1afc0c4fc9272ec)) +- **td-headers-attr:** report headers attribute referencing other elements as unsupported ([#4589](https://github.com/dequelabs/axe-core/issues/4589)) ([ec7c6c8](https://github.com/dequelabs/axe-core/commit/ec7c6c8875970388c4fe2c73147a3dd43497161e)), closes [#3987](https://github.com/dequelabs/axe-core/issues/3987) + +### Bug Fixes + +- **aria-allowed-role:** add form to allowed roles of form element ([#4588](https://github.com/dequelabs/axe-core/issues/4588)) ([8aa47ac](https://github.com/dequelabs/axe-core/commit/8aa47ac01f9959b9d47ac24dcd2fd8c88c9279f7)), closes [/github.com/dequelabs/axe-core/blob/develop/lib/standards/html-elms.js#L264](https://github.com/dequelabs//github.com/dequelabs/axe-core/blob/develop/lib/standards/html-elms.js/issues/L264) +- **aria-allowed-role:** Add math to allowed roles for img element ([#4658](https://github.com/dequelabs/axe-core/issues/4658)) ([95b6c18](https://github.com/dequelabs/axe-core/commit/95b6c184872cf70c7f54a96aa813a9e8bc2c066d)), closes [#4657](https://github.com/dequelabs/axe-core/issues/4657) +- **autocomplete-valid :** Ignore readonly autocomplete field ([#4721](https://github.com/dequelabs/axe-core/issues/4721)) ([491f4ec](https://github.com/dequelabs/axe-core/commit/491f4ecdbbb79d019daa63debc03ac0efb47adf8)), closes [#4708](https://github.com/dequelabs/axe-core/issues/4708) +- **autocomplete-valid:** treat values "xon" and "xoff" as non-WCAG-violations ([#4878](https://github.com/dequelabs/axe-core/issues/4878)) ([52bc611](https://github.com/dequelabs/axe-core/commit/52bc61162aa170a30a38246ade099ba3fc10cc2a)), closes [#4877](https://github.com/dequelabs/axe-core/issues/4877) +- **axe.d.ts:** add typings for preload options object ([#4543](https://github.com/dequelabs/axe-core/issues/4543)) ([cfd2974](https://github.com/dequelabs/axe-core/commit/cfd297498c0e34edd5ff7e62935060bb9dda4db7)) +- **button-name,input-button-name,input-img-alt:** allow label to give accessible name ([#4607](https://github.com/dequelabs/axe-core/issues/4607)) ([a9710d7](https://github.com/dequelabs/axe-core/commit/a9710d757c6ca6ee0ce5d26be3427bab54b87a7a)), closes [#4472](https://github.com/dequelabs/axe-core/issues/4472) [#3696](https://github.com/dequelabs/axe-core/issues/3696) [#3696](https://github.com/dequelabs/axe-core/issues/3696) +- **captions:** fix grammar in captions check incomplete message ([#4661](https://github.com/dequelabs/axe-core/issues/4661)) ([11de515](https://github.com/dequelabs/axe-core/commit/11de515858a7c10a3d7400163fc2b834715152fc)) +- **color-contrast:** do not run on elements with font-size: 0 ([#4822](https://github.com/dequelabs/axe-core/issues/4822)) ([d77c885](https://github.com/dequelabs/axe-core/commit/d77c8854c847573597eccf54c00091a4a2134cfd)), closes [#4820](https://github.com/dequelabs/axe-core/issues/4820) +- consistently parse tabindex, following HTML 5 spec ([#4637](https://github.com/dequelabs/axe-core/issues/4637)) ([645a850](https://github.com/dequelabs/axe-core/commit/645a850f601f4f3f18cc4aaca399aad18a9fa5d2)), closes [#4632](https://github.com/dequelabs/axe-core/issues/4632) +- **core:** measure perf for async checks ([#4609](https://github.com/dequelabs/axe-core/issues/4609)) ([7e9bacf](https://github.com/dequelabs/axe-core/commit/7e9bacf1ecb8c53404fac3eeed087e370e2a9cfa)) +- fix grammar when using "alternative text" in a sentence ([#4811](https://github.com/dequelabs/axe-core/issues/4811)) ([237a586](https://github.com/dequelabs/axe-core/commit/237a5861b0fb044c885b154436696279deca7a13)), closes [#4394](https://github.com/dequelabs/axe-core/issues/4394) +- **get-ancestry:** add nth-child selector for multiple siblings of shadow root ([#4606](https://github.com/dequelabs/axe-core/issues/4606)) ([1cdd6c3](https://github.com/dequelabs/axe-core/commit/1cdd6c3e698a6a4c28604448284993c4c20ca272)), closes [#4563](https://github.com/dequelabs/axe-core/issues/4563) +- **get-ancestry:** don't error when there is no parent ([#4617](https://github.com/dequelabs/axe-core/issues/4617)) ([a005703](https://github.com/dequelabs/axe-core/commit/a0057039072f68bd605e8bacdca64692d57f612e)) +- **locale:** fix typos in japanese (ja) locale ([#4856](https://github.com/dequelabs/axe-core/issues/4856)) ([3462cc5](https://github.com/dequelabs/axe-core/commit/3462cc57e8480334c125c38b7ecb42344b405dd4)) +- **locale:** fixed typos in german (DE) locale ([#4631](https://github.com/dequelabs/axe-core/issues/4631)) ([b7736de](https://github.com/dequelabs/axe-core/commit/b7736deae9ec14a4e81182adb53be73f3cce9894)) +- **locale:** proofread and updated de.json ([#4643](https://github.com/dequelabs/axe-core/issues/4643)) ([8060ada](https://github.com/dequelabs/axe-core/commit/8060ada737a23cdf68bb5b4c95b8c0e2cca45dad)) +- **meta-viewport:** lower impact to moderate ([#4887](https://github.com/dequelabs/axe-core/issues/4887)) ([2f32aa5](https://github.com/dequelabs/axe-core/commit/2f32aa5bada78ffcfc965ed2b64be533263c6bd5)), closes [#4714](https://github.com/dequelabs/axe-core/issues/4714) +- **no-autoplay-audio:** don't timeout for preload=none media elements ([#4684](https://github.com/dequelabs/axe-core/issues/4684)) ([cdc871e](https://github.com/dequelabs/axe-core/commit/cdc871e68f3dbc6acbfed12d3ec63ea4da1a4065)) +- **performanceTimer:** throwing in axe catch clause ([#4852](https://github.com/dequelabs/axe-core/issues/4852)) ([a4ade04](https://github.com/dequelabs/axe-core/commit/a4ade04bc2ba93dcad8a24094fb0dc5edb6da8b2)), closes [/github.com/dequelabs/axe-core/blob/e7dae4ec48cbfef74de9f833fdcfb178c1002985/lib/core/base/rule.js#L297-L300](https://github.com/dequelabs//github.com/dequelabs/axe-core/blob/e7dae4ec48cbfef74de9f833fdcfb178c1002985/lib/core/base/rule.js/issues/L297-L300) +- **performanceTimer:** work in frames ([#4834](https://github.com/dequelabs/axe-core/issues/4834)) ([d7dfebc](https://github.com/dequelabs/axe-core/commit/d7dfebc0271d2970c0937024ce693a771885002c)) +- **rules:** Change "alternate text" to "alternative text" ([#4582](https://github.com/dequelabs/axe-core/issues/4582)) ([b03ada3](https://github.com/dequelabs/axe-core/commit/b03ada3dd3b6490bb88e366bf807b10e1a4038a4)) +- **target-size:** do not treat focusable tabpanels as targets ([#4702](https://github.com/dequelabs/axe-core/issues/4702)) ([60d11f2](https://github.com/dequelabs/axe-core/commit/60d11f2d01b9e859e54a15ae0232b8b5d1c48d35)), closes [#4421](https://github.com/dequelabs/axe-core/issues/4421) [#4701](https://github.com/dequelabs/axe-core/issues/4701) +- **type:** correct RuleError type ([#4893](https://github.com/dequelabs/axe-core/issues/4893)) ([d1aa8e2](https://github.com/dequelabs/axe-core/commit/d1aa8e2094031159b041a3e9cf2c796a26b3daba)) +- **types:** correct raw types ([#4903](https://github.com/dequelabs/axe-core/issues/4903)) ([3eade11](https://github.com/dequelabs/axe-core/commit/3eade110a7ac173e3537e8eb18dd0db026f13c75)) + +### [4.10.3](https://github.com/dequelabs/axe-core/compare/v4.10.2...v4.10.3) (2025-03-04) + +### Bug Fixes + +- **aria-allowed-role:** Add math to allowed roles for img element ([#4658](https://github.com/dequelabs/axe-core/issues/4658)) ([f6dddd9](https://github.com/dequelabs/axe-core/commit/f6dddd905bb86d2073d760d4c1ff39996e59a4f4)), closes [#4657](https://github.com/dequelabs/axe-core/issues/4657) +- **captions:** fix grammar in captions check incomplete message ([#4661](https://github.com/dequelabs/axe-core/issues/4661)) ([3ef7058](https://github.com/dequelabs/axe-core/commit/3ef7058d3a16a6898eaed718e39b34b45f9ed74f)) +- consistently parse tabindex, following HTML5 spec ([#4637](https://github.com/dequelabs/axe-core/issues/4637)) ([3b0a361](https://github.com/dequelabs/axe-core/commit/3b0a3619d5e6147d8885725cce196868dac89f7f)), closes [#4632](https://github.com/dequelabs/axe-core/issues/4632) +- **core:** measure perf for async checks ([#4609](https://github.com/dequelabs/axe-core/issues/4609)) ([e7dc26e](https://github.com/dequelabs/axe-core/commit/e7dc26e6cbaf9089611853805e05216b6529e1f9)) +- **locale:** fixed typos in german (DE) locale ([#4631](https://github.com/dequelabs/axe-core/issues/4631)) ([0740980](https://github.com/dequelabs/axe-core/commit/07409802115c199a68f58dcaf7467e35b4867140)) +- **locale:** proofread and updated de.json ([#4643](https://github.com/dequelabs/axe-core/issues/4643)) ([910cdb2](https://github.com/dequelabs/axe-core/commit/910cdb20702ce116c781d58f021adc05095ffcbb)) +- **no-autoplay-audio:** don't timeout for preload=none media elements ([#4684](https://github.com/dequelabs/axe-core/issues/4684)) ([b7f1ad1](https://github.com/dequelabs/axe-core/commit/b7f1ad1ccf719b7149a5ef1805b405707a474e31)) +- **target-size:** do not treat focusable tabpanels as targets ([#4702](https://github.com/dequelabs/axe-core/issues/4702)) ([67d4e4f](https://github.com/dequelabs/axe-core/commit/67d4e4f7d0d0b803b66f216ff86f401649ed024f)), closes [#4421](https://github.com/dequelabs/axe-core/issues/4421) [#4701](https://github.com/dequelabs/axe-core/issues/4701) + +### [4.10.2](https://github.com/dequelabs/axe-core/compare/v4.10.1...v4.10.2) (2024-10-21) + +### Bug Fixes + +- **get-ancestry:** don't error when there is no parent ([#4617](https://github.com/dequelabs/axe-core/issues/4617)) ([6c07102](https://github.com/dequelabs/axe-core/commit/6c07102b1d29145b8dc5f1d96229f3d0b8b38068)) + +### [4.10.1](https://github.com/dequelabs/axe-core/compare/v4.10.0...v4.10.1) (2024-10-16) + +### Bug Fixes + +- **aria-allowed-role:** add form to allowed roles of form element ([#4588](https://github.com/dequelabs/axe-core/issues/4588)) ([d462d67](https://github.com/dequelabs/axe-core/commit/d462d674bb7de0848ce2695f80b95d677c5016e0)), closes [/github.com/dequelabs/axe-core/blob/develop/lib/standards/html-elms.js#L264](https://github.com/dequelabs//github.com/dequelabs/axe-core/blob/develop/lib/standards/html-elms.js/issues/L264) +- **axe.d.ts:** add typings for preload options object ([#4543](https://github.com/dequelabs/axe-core/issues/4543)) ([72e269f](https://github.com/dequelabs/axe-core/commit/72e269f1e6d6039e70e614005f04ebfd3fe5aca5)) +- **button-name,input-button-name,input-img-alt:** allow label to give accessible name ([#4607](https://github.com/dequelabs/axe-core/issues/4607)) ([364eb72](https://github.com/dequelabs/axe-core/commit/364eb72bb8f20b0ffc31be24cc96cbd349c301cb)), closes [#4472](https://github.com/dequelabs/axe-core/issues/4472) [#3696](https://github.com/dequelabs/axe-core/issues/3696) [#3696](https://github.com/dequelabs/axe-core/issues/3696) +- **get-ancestry:** add nth-child selector for multiple siblings of shadow root ([#4606](https://github.com/dequelabs/axe-core/issues/4606)) ([bdd94a2](https://github.com/dequelabs/axe-core/commit/bdd94a227a95cd5b9f8e2a1e0fd259ddd702810c)), closes [#4563](https://github.com/dequelabs/axe-core/issues/4563) +- **rules:** Change "alternate text" to "alternative text" ([#4582](https://github.com/dequelabs/axe-core/issues/4582)) ([31e0f61](https://github.com/dequelabs/axe-core/commit/31e0f61ca871b3df86468577c449a02c8ece12f0)) + +## [4.10.0](https://github.com/dequelabs/axe-core/compare/v4.9.1...v4.10.0) (2024-07-29) + +### Features + +- **new-rule:** summary elements must have an accessible name ([#4511](https://github.com/dequelabs/axe-core/issues/4511)) ([0d8a99e](https://github.com/dequelabs/axe-core/commit/0d8a99eadd8d49e5d3ea0f11ad77be732148431e)), closes [#4510](https://github.com/dequelabs/axe-core/issues/4510) + +### Bug Fixes + +- **aria-allowed-attr:** allow aria-multiline=false for element with contenteditable ([#4537](https://github.com/dequelabs/axe-core/issues/4537)) ([f019068](https://github.com/dequelabs/axe-core/commit/f0190685722495d00be644cabb1c9741d74acdea)) +- **aria-allowed-attr:** allow aria-required=false when normally not allowed ([#4532](https://github.com/dequelabs/axe-core/issues/4532)) ([2e242e1](https://github.com/dequelabs/axe-core/commit/2e242e146929902c97e181e41fa45e656cf3eb51)) +- **aria-prohibited-attr:** allow aria-label/ledby on descendants of widget ([#4541](https://github.com/dequelabs/axe-core/issues/4541)) ([07c5d91](https://github.com/dequelabs/axe-core/commit/07c5d91c658bda6bcd2743950bf70f25abd1f9ae)) +- **aria-roledescription:** keep disabled with { runOnly: 'wcag2a' } ([#4526](https://github.com/dequelabs/axe-core/issues/4526)) ([5b4cb9d](https://github.com/dequelabs/axe-core/commit/5b4cb9d7992a4c07745e64708040777de64874bd)), closes [#4523](https://github.com/dequelabs/axe-core/issues/4523) +- **autocomplete-valid:** incomplete for invalid but safe values ([#4500](https://github.com/dequelabs/axe-core/issues/4500)) ([e31a974](https://github.com/dequelabs/axe-core/commit/e31a974de395845c08af345f9458a8091e2b1c4b)), closes [#4492](https://github.com/dequelabs/axe-core/issues/4492) +- **build:** limit locales to valid files when using the --all-lang option ([#4486](https://github.com/dequelabs/axe-core/issues/4486)) ([d3db593](https://github.com/dequelabs/axe-core/commit/d3db593991261ad44eef1c142d8a4646edde93fa)), closes [#4485](https://github.com/dequelabs/axe-core/issues/4485) +- Prevent errors when loading axe in Angular + Jest ([#4456](https://github.com/dequelabs/axe-core/issues/4456)) ([3ef9353](https://github.com/dequelabs/axe-core/commit/3ef93531a574c2be76a92d59599d978714cca9d0)), closes [#4400](https://github.com/dequelabs/axe-core/issues/4400) +- Minor grammatical fixes for some rules and checks ([#4499](https://github.com/dequelabs/axe-core/issues/4499)) ([11fad59](https://github.com/dequelabs/axe-core/commit/11fad598c25eadd29f35ef6be382d907057d4537)) +- **landmark-unique:** follow spec, aside -> landmark ([#4469](https://github.com/dequelabs/axe-core/issues/4469)) ([e32f803](https://github.com/dequelabs/axe-core/commit/e32f8034246a92e4132dc04f6310e2b414d6d43f)), closes [#4460](https://github.com/dequelabs/axe-core/issues/4460) +- **required-attr:** allow aria-valuetext on slider instead of valuenow ([#4518](https://github.com/dequelabs/axe-core/issues/4518)) ([135898b](https://github.com/dequelabs/axe-core/commit/135898b38d5eb46c42170527a0ac9add425c5c3d)), closes [#4515](https://github.com/dequelabs/axe-core/issues/4515) + +### [4.9.1](https://github.com/dequelabs/axe-core/compare/v4.9.0...v4.9.1) (2024-05-06) + +### Bug Fixes + +- Prevent errors when loading axe in a page with prototype.js +- **aria-allowed-attr:** allow meter role allowed aria-\* attributes on meter element ([#4435](https://github.com/dequelabs/axe-core/issues/4435)) ([7ac6392](https://github.com/dequelabs/axe-core/commit/7ac63921e7fab21f3359dcfc8affa7585bc9c25b)) +- **aria-allowed-role:** add gridcell, separator, slider and treeitem to allowed roles of button element ([#4398](https://github.com/dequelabs/axe-core/issues/4398)) ([4788bf8](https://github.com/dequelabs/axe-core/commit/4788bf8d6fd963d7b017dad950b122ffcea8d151)) +- **aria-roles:** correct abstract roles (types) for aria-roles([#4421](https://github.com/dequelabs/axe-core/pull/4421)) +- **aria-valid-attr-value:** aria-controls & aria-haspopup incomplete ([#4418](https://github.com/dequelabs/axe-core/pull/4418)) +- fix building axe-core translation files with region locales ([#4396](https://github.com/dequelabs/axe-core/issues/4396)) ([5c318f3](https://github.com/dequelabs/axe-core/commit/5c318f3537056be5779cb53374bc6f4785947c91)), closes [#4388](https://github.com/dequelabs/axe-core/issues/4388) +- **invalidrole:** allow upper and mixed case role names ([#4358](https://github.com/dequelabs/axe-core/issues/4358)) ([105016c](https://github.com/dequelabs/axe-core/commit/105016cfe9d82876cfed2ff5c656a7842c5b3761)), closes [#2695](https://github.com/dequelabs/axe-core/issues/2695) +- **isVisibleOnScreen:** account for position: absolute elements inside overflow container ([#4405](https://github.com/dequelabs/axe-core/issues/4405)) ([2940f6e](https://github.com/dequelabs/axe-core/commit/2940f6ee36ba52d8cf089be2a3c8e7c516c81dd6)), closes [#4016](https://github.com/dequelabs/axe-core/issues/4016) +- **label-content-name-mismatch:** better dismiss and wysiwyg symbolic text characters ([#4402](https://github.com/dequelabs/axe-core/issues/4402)) +- **region:** Decorative images ignored by region rule ([#4412](https://github.com/dequelabs/axe-core/pull/4412)) +- **target-size:** ignore descendant elements in shadow dom ([#4410](https://github.com/dequelabs/axe-core/issues/4410)) ([6091367](https://github.com/dequelabs/axe-core/commit/6091367a20f70e536fc7e8d77eae4fa7232bc7c0)) +- **target-size:** pass for element that has nearby elements that are obscured ([#4422](https://github.com/dequelabs/axe-core/issues/4422)) ([3a90bb7](https://github.com/dequelabs/axe-core/commit/3a90bb70c8db087b2f03cc30a4aee756995c311c)), closes [#4387](https://github.com/dequelabs/axe-core/issues/4387) + ## [4.9.0](https://github.com/dequelabs/axe-core/compare/v4.8.4...v4.9.0) (2024-03-25) ### Features diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..8a81ac332 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,102 @@ +# axe-core — Claude Code Context + +**Last updated:** 2026-03-19 + +## 0. Fundamental Standards + +- **Formatting:** Run `npm run fmt` (Prettier) and `npm run eslint` before every commit. No exceptions. +- **Zero-Exception Testing:** 100% coverage goal. Run `npm test` before completion. `feat`/`fix` and other behavior-changing PRs require unit + integration tests where applicable; see `doc/code-submission-guidelines.md` for exceptions (e.g., some `chore`/docs changes). +- **Import Rule:** Directory-level import restrictions are strictly enforced. See `eslint.config.js`. +- **Commits:** Angular commit convention is mandatory. PRs with non-conforming commits will be rejected. See `doc/code-submission-guidelines.md`. +- **Issues:** All unresolved issues tracked in [GitHub Issues](https://github.com/dequelabs/axe-core/issues). + +## 1. Technical Guidelines + +### Code Structure + +- **Return Early:** Keep the happy path left-aligned. Handle errors/edge cases first with early returns — never nest when you can exit. +- **Exports:** Default export at the top of the file, immediately after imports. +- **JSDoc:** Add JSDoc/DocBlock comments where appropriate, especially for exported APIs and complex logic. Document parameters and return types. See `doc/code-submission-guidelines.md` (source of truth) and `doc/developer-guide.md` for additional guidance. +- **Naming:** Files and rule/check IDs in kebab-case. Functions in camelCase. Booleans prefixed `is`/`has`/`should`. Constants in `UPPER_SNAKE_CASE`. +- **Variables:** Declare at point of use, not at the top of the function. + +### Virtual Nodes vs. HTMLElement + +- **Prefer Virtual Nodes** for attribute access and property reads (`virtualNode.attr()`, `virtualNode.props`). +- **Use real DOM** only when you need DOM APIs (e.g., `getBoundingClientRect`, `getRootNode`). +- **`core/utils/` functions** should default to real DOM inputs/outputs, except utilities that are explicitly documented as operating on `VirtualNode` (for example, tree or selector helpers). Avoid introducing new `VirtualNode`-dependent utilities unless there is a clear performance or API benefit. +- **Conversion:** Use `getNodeFromTree()` from `core/utils` when you receive an ambiguous input (such as a `VirtualNode`, selector, or mixed type) and need to resolve it to a real DOM `Node`. + +### Import Restrictions (Hard Rules) + +- `standards/` → nothing (pure data, no imports). +- `core/utils/` → other `core/utils`, `core/base`, `standards` via **direct file paths only** (no index). +- `core/imports/` → node modules **only** (the only place npm imports are allowed). +- `commons/` → other `commons` (direct paths), `core/utils` (index OK). +- `checks/` and `rules/` → any directory (index OK). +- **Never** import `commons` from `core/utils`. This is the most commonly rejected violation. + +### Checks & Rules + +- **Check evaluate functions:** Return `true` (pass), `false` (fail), or `undefined` (incomplete). Use `this.data()` to pass values to message templates and to provide incomplete-result detail. +- **Rule JSON:** Use `all`, `any`, `none` check arrays. `selector` + optional `matches` to scope candidates. Valid `impact` values: `"minor"`, `"moderate"`, `"serious"`, `"critical"`. +- **Standards data:** Never hardcode ARIA/HTML lists in checks. Query from `standards/` via `commons/standards` functions. +- **Messages:** All user-facing strings live in check/rule JSON `metadata.messages`. Use `${data.property}` templates. Support singular/plural variants. Update `locales/_template.json` whenever messages change (auto-synced on `npm run build`). + +### High-Risk Areas (Extra Scrutiny Required) + +- **Color contrast:** Handles all CSS color formats, opacity, blend modes, stacking contexts, text-shadow. Return `undefined` when background cannot be determined. See `doc/developer-guide.md`. +- **ARIA validation:** Must stay current with spec. Query roles/attrs from `standards/aria-roles.js` and `standards/aria-attrs.js`. Handle implicit vs. explicit roles. See `doc/rule-development.md`. +- **Hidden elements:** Use `isVisibleToScreenReaders()`, not CSS visibility alone. Account for `aria-hidden="true"` and Shadow DOM boundaries. +- **i18n:** Update `locales/_template.json` on every message change and commit the generated file alongside source. + +## 2. Testing + +- **Structure:** Mirror `lib/` exactly under `test/`. File `lib/commons/text/sanitize.js` → `test/commons/text/sanitize.js`. +- **Checks:** Use `axe.testUtils.MockCheckContext()`. Only reset `checkContext` in `afterEach` — `fixture` and `axe._tree` are auto-cleared. +- **Integration tests:** All rule changes require an HTML + JSON pair. Use `test/integration/rules//` for mocha-hosted tests or `test/integration/full//` when the rule requires a full HTML page. JSON selectors must use axe array format (`["#id"]`; iframes: `["iframe", "#id"]`). Also update or create virtual-rules tests where appropriate. +- **Shadow DOM:** Every relevant check/rule must include an open Shadow DOM test case using `queryShadowFixture`. +- **Logging:** Do not commit `console.log` statements. + +## 3. Build & Commits + +- **Bundles (`axe.js`, `axe.min.js`)** are auto-generated for releases/publishing and are not committed to the repo (they are gitignored). +- **Locales template (`locales/_template.json`)** is auto-generated. When message strings change, regenerate this file and commit it in the same commit as the source changes — never in a separate commit. +- **One change per PR.** Do not mix refactoring with feature work. +- **Commit format:** `(): ` — imperative, lowercase, no period, ≤100 chars total. Body explains motivation. Footer: `Closes issue #123` or full URL. See `doc/code-submission-guidelines.md` for the full type list. + +**Example:** + +``` +fix(aria-valid-attr-value): handle multiple aria-errormessage IDs + +When aria-errormessage contains multiple space-separated IDs, verify +all IDs exist in aria-describedby instead of matching the full string. + +Closes issue #4957 +``` + +## 4. Documentation & API Changes + +- **New rules:** Update `CHANGELOG.md`. `doc/rule-descriptions.md` is auto-generated by `npm run build`. +- **API changes:** Update `doc/API.md` and `axe.d.ts` TypeScript definitions. +- **Breaking changes:** Avoid breaking changes — prefer supporting both old and new formats simultaneously. If unavoidable, add `BREAKING CHANGE: description` to commit footer, include migration guide in `CHANGELOG.md`, and tag deprecated code with `@deprecated` JSDoc. + +## 5. Examples (Copy-Paste Reference) + +- **Code patterns:** `doc/examples/code-patterns.md` — return early, default export, imports, JSDoc, Virtual Node usage +- **Test patterns:** `doc/examples/test-patterns.md` — unit tests, check tests, Shadow DOM tests, integration test HTML+JSON +- **Rule & check templates:** `doc/examples/rule-check-templates.md` — JSON templates for rules and checks, evaluate function pattern +- **PR review patterns:** `doc/examples/pr-review-patterns.md` — common reviewer feedback, anti-patterns, what reviewers look for + +## 6. Reference Docs & Help + +- **Contributing guide:** `CONTRIBUTING.md` +- **Import rules detail:** `eslint.config.js` +- **Code submission standards:** `doc/code-submission-guidelines.md` +- **Developer guide:** `doc/developer-guide.md` +- **Rule development:** `doc/rule-development.md` +- **API reference:** `doc/API.md` +- **Pull Request Checklist:** `doc/pull-request-checklist.md` +- **Slack:** [axe-community](https://accessibility.deque.com/axe-community) +- **Issues:** [GitHub Issues](https://github.com/dequelabs/axe-core/issues) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d692677a2..1719460ca 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. @@ -31,7 +71,7 @@ We expect all code to be 100% covered by tests. We don't have or want code cover Tests should be added to the `test` directory using the same file path and name of the source file the test is for. For example, the source file `lib/commons/text/sanitize.js` should have a test file at `test/commons/text/sanitize.js`. -Axe uses Karma / Mocha / Chai as its testing framework. +Axe uses Web Test Runner / Mocha / Chai / Sinon as its testing framework. ### Documentation and Comments @@ -162,13 +202,12 @@ 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 Web Test Runner server. Press `D` to open the Chrome browser. Click the link to a test to start debugging. 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:*`: +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 `files` argument when using the debug command and pass a test files glob pattern. ```console -# accepts a single directory or a comma-separated list of directories -npm run test:debug -- testDirs=core,commons +npm run test:debug -- --files 'test/core' ``` ## Using axe with TypeScript diff --git a/Gruntfile.js b/Gruntfile.js index 0a888fe1c..f7022069d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -22,9 +22,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 = ['']; @@ -32,7 +36,7 @@ module.exports = function (grunt) { // run tests only for affected files instead of all tests grunt.event.on('watch', function (action, filepath) { - grunt.config.set('watch.file', filepath); + grunt.option('changed-file', filepath); }); process.env.NODE_NO_HTTP2 = 1; // to hide node warning - (node:18740) ExperimentalWarning: The http2 module is an experimental API. @@ -41,7 +45,7 @@ module.exports = function (grunt) { pkg: grunt.file.readJSON('package.json'), clean: { core: ['dist', 'tmp/core', 'tmp/rules.js', 'axe.js', 'axe.*.js'], - tests: ['tmp/integration-tests.js'] + tests: ['tmp/integration-tests'] }, babel: { options: { @@ -104,6 +108,39 @@ module.exports = function (grunt) { cwd: 'lib/core', src: ['core.js'], dest: 'tmp/core' + }, + // build so we can test it by itself + { + expand: true, + cwd: 'lib/gather-internals', + src: ['walk-tree.js'], + dest: 'tmp/', + options: { + globalName: '_gatherInternals' + } + }, + { + expand: true, + src: ['lib/gather-internals/main.js'], + dest: './', + options: { + outfile: 'gather-internals.js', + // esbuild doesn't support returning from an iife so we'll have to do a bit of a hack to make it work + // @see https://github.com/evanw/esbuild/issues/2277 + banner: { + js: '(() => {' + }, + footer: { + js: `return elementInternalsMap; +})();` + }, + globalName: 'elementInternalsMap', + metafile: true + }, + validateImports: { + max: 10, + maxSize: 4000 + } } ] } @@ -225,9 +262,7 @@ module.exports = function (grunt) { } }, test: { - data: { - testFile: '<%= watch.file %>' - } + data: {} }, watch: { axe: { @@ -237,7 +272,7 @@ module.exports = function (grunt) { }, tests: { options: { spawn: false }, - files: ['test/**/*'], + files: ['test/**/*', '!test/integration/full/**/*'], tasks: ['test'] } }, diff --git a/README.md b/README.md index 1f6ccfec4..9be7bd46e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # axe-core -[![License](https://img.shields.io/npm/l/axe-core.svg)](LICENSE) +[![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) -[![Total npm downloads](https://img.shields.io/npm/dt/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)](https://github.com/dequelabs/axe-core/graphs/contributors) +[![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) @@ -14,7 +14,7 @@ Axe is an accessibility testing engine for websites and other HTML-based user in ## The Accessibility Rules -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 found in [doc/rule-descriptions.md](./doc/rule-descriptions.md). +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). With axe-core, you can find **on average 57% of WCAG issues automatically**. Additionally, axe-core will return elements as "incomplete" where axe-core could not be certain, and manual review is needed. @@ -65,7 +65,7 @@ Axe was built to reflect how web development actually works. It works with all m - 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 supports in-memory fixtures, static fixtures, integration tests, and iframes of infinite depth. - Axe is highly configurable. ## Supported Browsers @@ -103,9 +103,9 @@ or equivalently: 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 fin 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 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 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. +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: @@ -138,21 +138,21 @@ axe.configure({ 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 -- Greek -- Italian -- Simplified Chinese -- Traditional Chinese ## Updates & Security diff --git a/axe.d.ts b/axe.d.ts index 8ecd9de75..bd6933645 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,22 @@ declare namespace axe { iframes?: boolean; elementRef?: boolean; frameWaitTime?: number; - preload?: boolean; + preload?: boolean | PreloadOptions; performanceTimer?: boolean; pingWaitTime?: number; } + interface NormalizedRunOptions extends RunOptions { + runOnly?: RunOnly; + } + 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 +170,9 @@ declare namespace axe { tags: TagValue[]; nodes: NodeResult[]; } + interface IncompleteResult extends Result { + error?: Omit; + } interface NodeResult { html: string; impact?: ImpactValue; @@ -197,6 +210,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; } @@ -306,7 +334,7 @@ declare namespace axe { matches?: string | ((node: Element, virtualNode: VirtualNode) => boolean); reviewOnFail?: boolean; actIds?: string[]; - metadata?: Omit; + metadata?: Omit; } interface AxePlugin { id: string; @@ -324,6 +352,7 @@ declare namespace axe { helpUrl: string; tags: string[]; actIds?: string[]; + enabled?: boolean; } interface SerialDqElement { source: string; @@ -335,6 +364,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 +390,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,7 +434,47 @@ 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 { + getElementSource: ( + element: Node | null | undefined, + options?: { maxLength?: number; attrLimit?: number } + ) => string; getFrameContexts: ( context?: ElementContext, options?: RunOptions @@ -406,15 +482,30 @@ 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; + normalizeRunOptions: (options?: RunOptions) => NormalizedRunOptions; } interface Aria { @@ -424,6 +515,7 @@ declare namespace axe { interface Dom { isFocusable: (node: Element | VirtualNode) => boolean; isNativelyFocusable: (node: Element | VirtualNode) => boolean; + getNodeGrid: (node: Node | VirtualNode) => Grid; } type AccessibleTextOptions = { @@ -531,13 +623,23 @@ declare namespace axe { * @param {Array} tags Optional array of tags * @return {Array} Array of rules */ - function getRules(tags?: string[]): RuleMetadata[]; + function getRules( + tags?: string[] + ): (Omit & { enabled: boolean })[]; /** * Restores the default axe configuration */ function reset(): void; + /** + * Restores the default locale that was active before any + * `axe.configure({ locale })` call. No-op if no non-default + * locale has ever been applied. Does not affect any other + * configuration. + */ + function resetLocale(): void; + /** * Function to register a plugin configuration in document and its subframes * @param {Object} plugin A plugin configuration object @@ -583,6 +685,31 @@ declare namespace axe { isDefault?: boolean ): void; + /** + * Run axe in the current window only + * @param {ElementContext} context Optional The `Context` specification object @see Context + * @param {RunOptions} options Optional Options passed into rules or checks, temporarily modifying them. + * @returns {Promise} Partial result, for use in axe.finishRun. + */ + function externalAPIs(params?: { + elementInternalsTimeout?: number; + getElementInternals?: () => Promise; + }): void; + + type ElementInternalsMap = Array<{ + ancestry: CrossTreeSelector; + internals: Record; + }>; + type InternalsData = string | InternalsDataIdref | InternalsDataIdrefs; + type InternalsDataIdref = { + type: 'HTMLElement'; + value: CrossTreeSelector; + }; + type InternalsDataIdrefs = { + type: 'NodeList'; + value: CrossTreeSelector[]; + }; + // axe.frameMessenger type FrameMessenger = { open: (topicHandler: TopicHandler) => Close | void; diff --git a/bower.json b/bower.json index bff620abe..ee0f36409 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "axe-core", - "version": "4.9.0", + "version": "4.12.1", "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/cherry-pick.js b/build/cherry-pick.js old mode 100644 new mode 100755 index 2b3dc2afe..c5575f364 --- a/build/cherry-pick.js +++ b/build/cherry-pick.js @@ -1,8 +1,13 @@ +#!/usr/bin/env node + const { execSync } = require('child_process'); const conventionalCommitsParser = require('conventional-commits-parser'); const chalk = require('chalk'); const { version } = require('../package.json'); +// Node's default execSync buffer (~1 MiB) is too small for `git log` on long histories. +const GIT_LOG_EXEC_OPTS = { maxBuffer: 50 * 1024 * 1024 }; + const releaseType = process.argv[2]; let ignoreCommits = process.argv[3]; @@ -48,7 +53,8 @@ function getCommits(branch) { // all commits are too large for execSync buffer size so we'll just get since the last 3 years const date = new Date(new Date().setFullYear(new Date().getFullYear() - 3)); const stdout = execSync( - `git log ${branch || ''} --abbrev-commit --since=${date.getFullYear()}` + `git log ${branch || ''} --no-merges --abbrev-commit --since=${date.getFullYear()}`, + GIT_LOG_EXEC_OPTS ).toString(); const allCommits = stdout .split(/commit (?=[\w\d]{8}[\n\r])/) @@ -129,7 +135,7 @@ commitsToCherryPick.forEach(({ hash, type, scope, subject }) => { try { execSync(`git cherry-pick ${hash} -X theirs`); - } catch (e) { + } catch { console.error( chalk.red.bold('\nAborting cherry-pick and reseting to master') ); 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/generate-integration-tests.js b/build/generate-integration-tests.js new file mode 100644 index 000000000..0143e28c7 --- /dev/null +++ b/build/generate-integration-tests.js @@ -0,0 +1,60 @@ +'use strict'; + +/** + * Generates test files for integration rule tests in tmp/integration-tests/. + * This replaces the Karma preprocessor (test/integration/rules/preprocessor.js) + * that combined *.json + *.html pairs into executable JS at serve-time. + * + * Output files are picked up by web-test-runner via the test:unit:integration script. + */ + +const path = require('path'); +const fs = require('fs'); +const { globSync } = require('glob'); + +const rootDir = path.join(__dirname, '..'); +const rulesDir = path.join(rootDir, 'test', 'integration', 'rules'); +const outDir = path.join(rootDir, 'tmp', 'integration-tests'); +const runnerTemplate = fs.readFileSync( + path.join(rulesDir, 'runner.js'), + 'utf-8' +); + +// Clean and recreate output directory +fs.rmSync(outDir, { recursive: true, force: true }); +fs.mkdirSync(outDir, { recursive: true }); + +const jsonFiles = globSync('**/*.json', { cwd: rulesDir }); + +let count = 0; +for (const relPath of jsonFiles) { + const jsonPath = path.join(rulesDir, relPath); + const htmlPath = jsonPath.replace(/\.json$/, '.html'); + + if (!fs.existsSync(htmlPath)) { + // Some JSON files may not have a sibling HTML (e.g. nested frame fixtures) + continue; + } + + let test; + try { + test = JSON.parse(fs.readFileSync(jsonPath, 'utf-8')); + } catch (e) { + throw new Error(`Unable to parse ${jsonPath}: ${e.message}`); + } + + const html = fs.readFileSync(htmlPath, 'utf-8'); + test.content = html; + + const outPath = path.join(outDir, relPath.replace(/\.json$/, '.test.js')); + const outDirForFile = path.dirname(outPath); + fs.mkdirSync(outDirForFile, { recursive: true }); + + const output = runnerTemplate.replace('{}; /*tests*/', JSON.stringify(test)); + fs.writeFileSync(outPath, output, 'utf-8'); + count++; +} + +console.log( + `Generated ${count} integration test files in tmp/integration-tests/` +); diff --git a/build/next-version.js b/build/next-version.js deleted file mode 100755 index 189311c0a..000000000 --- a/build/next-version.js +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); -const assert = require('assert'); - -const pkgFile = path.resolve(__dirname, '..', 'package.json'); -const pkg = JSON.parse(fs.readFileSync(pkgFile)); - -const { CIRCLE_SHA1, CIRCLE_BRANCH } = process.env; -assert(CIRCLE_BRANCH, 'CIRCLE_BRANCH environment variable not set'); -assert(CIRCLE_SHA1, 'CIRCLE_SHA1 environment variable not set'); -assert( - CIRCLE_BRANCH === 'develop', - 'This script should only be run from "develop"' -); - -// Shorten the SHA -const GIT_SHA = CIRCLE_SHA1.substr(0, 7); - -// Strip the "dist tag" from the version (if it exists) -const version = pkg.version.replace(/-\w+\.\w+$/, ''); -const nextVersion = `${version}-canary.${GIT_SHA}`; -console.log(nextVersion); 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/build/tasks/esbuild.js b/build/tasks/esbuild.js index 30dbeb4ef..d157766e3 100644 --- a/build/tasks/esbuild.js +++ b/build/tasks/esbuild.js @@ -1,5 +1,6 @@ const { build } = require('esbuild'); const path = require('path'); +const assert = require('assert'); module.exports = function (grunt) { grunt.registerMultiTask( @@ -12,6 +13,7 @@ module.exports = function (grunt) { files.forEach(file => { const src = Array.isArray(file.src) ? file.src : [file.src]; const dest = file.dest; + const options = file.options || {}; src.forEach(entry => { const name = path.basename(entry); @@ -23,9 +25,29 @@ module.exports = function (grunt) { entryPoints: [entry], outfile: path.join(dest, name), minify: false, - bundle: true + bundle: true, + ...options }) - .then(done) + .then(result => { + if (options.metafile && file.validateImports) { + const { max, maxSize } = file.validateImports; + const { inputs } = result.metafile; + const entries = Object.entries(inputs); + + assert( + entries.length <= max, + `${entry} imported too many files (max: ${max}): ${entries.length}` + ); + for (const [key, value] of entries) { + assert( + value.bytes <= maxSize, + `${key} import size too large (max: ${maxSize}): ${value.bytes}` + ); + } + } + + done(result); + }) .catch(e => { grunt.fail.fatal(e); done(); diff --git a/build/tasks/metadata-function-map.js b/build/tasks/metadata-function-map.js index e2e9d6054..36b583670 100644 --- a/build/tasks/metadata-function-map.js +++ b/build/tasks/metadata-function-map.js @@ -22,7 +22,7 @@ module.exports = function (grunt) { '// This file is automatically generated using build/tasks/metadata-function-map.js\n'; src.forEach(globPath => { - glob.sync(globPath).forEach(filePath => { + glob.sync(globPath, { posix: true }).forEach(filePath => { const relativePath = path.relative( path.dirname(file.dest), filePath diff --git a/build/tasks/test.js b/build/tasks/test.js index c1f51a7b4..13e6a3d2b 100644 --- a/build/tasks/test.js +++ b/build/tasks/test.js @@ -1,5 +1,6 @@ const execSync = require('child_process').execSync; const chalk = require('chalk'); +const path = require('node:path'); /*eslint-env node */ ('use strict'); @@ -9,12 +10,37 @@ module.exports = function (grunt) { 'test', 'This task runs unit tests based on which file was changed', function () { - const testFile = this.data.testFile; + const testFile = grunt.option('changed-file'); console.log(`${chalk.green('>>')} File "${testFile}"`); + const files = []; - execSync(`npm run test:unit -- testFiles=${testFile}`, { - stdio: 'inherit' - }); + // build the integration tests before testing + if ( + (testFile.startsWith(path.join('test', 'integration', 'rules')) && + testFile.endsWith('.html')) || + testFile.endsWith('.json') + ) { + execSync('npm run build:integration-tests', { stdio: 'inherit' }); + const rule = testFile.split(path.sep)[3]; + files.push( + path.join('tmp', 'integration-tests', rule, rule + '.test.js') + ); + } + + if ( + testFile && + testFile.startsWith(`test${path.sep}`) && + testFile.endsWith('.js') + ) { + files.push(testFile); + } + + let cmd = 'npm run test:unit'; + if (files.length) { + cmd += ` -- --files "${files.join(',')}"`; + } + + execSync(cmd, { stdio: 'inherit' }); } ); }; diff --git a/build/tasks/validate.js b/build/tasks/validate.js index 9af5edb28..ea601806c 100644 --- a/build/tasks/validate.js +++ b/build/tasks/validate.js @@ -14,7 +14,7 @@ function fileExists(v, o) { var exists; try { exists = fs.existsSync(file); - } catch (e) { + } catch { return false; } return exists; @@ -349,6 +349,12 @@ const standardsTags = [ standardRegex: /^EN-301-549$/, criterionRegex: /^EN-9\.[1-4]\.[1-9]\.\d{1,2}$/, wcagLevelRegex: /^wcag21?aa?$/ + }, + { + name: 'RGAA', + standardRegex: /^RGAAv4$/, + criterionRegex: /^RGAA-\d{1,2}\.\d{1,2}\.\d{1,2}$/, + wcagLevelRegex: /^wcag21?aa?$/ } ]; @@ -399,7 +405,7 @@ function findTagIssues(tags) { standardTag: standardTags[0] ?? null, criterionTags }; - if (bestPracticeTags.length !== 0) { + if (name !== 'RGAA' && bestPracticeTags.length !== 0) { issues.push(`${name} tags cannot be used along side best-practice tag`); } if (standardTags.length === 0) { @@ -411,7 +417,7 @@ function findTagIssues(tags) { issues.push(`Expected at least one ${name} criterion tag, got 0`); } - if (wcagLevelRegex) { + if (wcagLevelRegex && standards.WCAG) { const wcagLevel = standards.WCAG.standardTag; if (!wcagLevel.match(wcagLevelRegex)) { issues.push(`${name} rules not allowed on ${wcagLevel}`); diff --git a/doc/API.md b/doc/API.md index ea1f04a44..f85a53e50 100644 --- a/doc/API.md +++ b/doc/API.md @@ -1,4 +1,4 @@ -# Axe Javascript Accessibility API +# Axe JavaScript Accessibility API ## Table of Contents @@ -10,6 +10,7 @@ 1. [API Name: axe.getRules](#api-name-axegetrules) 1. [API Name: axe.configure](#api-name-axeconfigure) 1. [API Name: axe.reset](#api-name-axereset) + 1. [API Name: axe.resetLocale](#api-name-axeresetlocale) 1. [API Name: axe.run](#api-name-axerun) 1. [Parameters axe.run](#parameters-axerun) 1. [Context Parameter](#context-parameter) @@ -23,7 +24,8 @@ 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. [API name: axe.externalAPIs](#api-name-axeexternal-apis) 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 +51,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 +60,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 +74,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 +96,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) | @@ -133,7 +137,7 @@ Returns a list of all rules with their ID and description - `tags` - **optional** Array of tags used to filter returned rules. If omitted, it will return all rules. See [axe-core tags](#axe-core-tags). -**Returns:** Array of rules that match the input filter with each entry having a format of `{ruleId: , description: , helpUrl: , help: , tags: }` +**Returns:** Array of rules that match the input filter with each entry having a format of `{ruleId: , description: , helpUrl: , help: , tags: , enabled: }`. `enabled` is `true` for rules that run by default when `axe.run()` is called with no options, and `false` for rules that are disabled by default (i.e. experimental and deprecated rules). #### Example 1 @@ -146,7 +150,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", @@ -159,10 +163,11 @@ In this example, we pass in the WCAG 2 A and AA tags into `axe.getRules` to retr "section508", "section508.22.a" ], - actIds: ['c487ae'] + actIds: ['c487ae'], + enabled: true }, { - 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", @@ -170,7 +175,8 @@ In this example, we pass in the WCAG 2 A and AA tags into `axe.getRules` to retr "cat.aria", "wcag2a", "wcag412" - ] + ], + enabled: true } … ] @@ -182,7 +188,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 +219,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 @@ -294,6 +300,24 @@ axe.reset(); None +### API Name: axe.resetLocale + +#### Description + +Restore the default locale that was active before any `axe.configure({ locale })` call, without touching the rest of the configuration. + +`axe.configure({ locale })` has no inverse, and `axe.reset()` also clears branding, rule enable/disable overrides, `frameMessenger`, and other configuration. `axe.resetLocale()` reverts only the locale (rule descriptions, check messages, failure summaries, `lang`) back to the default that was in effect before the first `applyLocale` call. It is a no-op if no non-default locale has ever been applied, and safe to call repeatedly. + +#### Synopsis + +```js +axe.resetLocale(); +``` + +#### Parameters + +None + ### API Name: axe.run #### Purpose @@ -567,7 +591,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 +694,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 +734,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 +746,6 @@ axe.run(document, function (err, results) { ###### `violations` - `violations[0]` - - `help` - `"

Introduction

' ); assert.isTrue(checkEvaluate.apply(checkContext, checkArgs)); }); - (shadowSupport.v1 ? it : xit)('should test Shadow tree content', function () { - var div = document.createElement('div'); - var shadow = div.attachShadow({ mode: 'open' }); + it('should test Shadow tree content', () => { + const div = document.createElement('div'); + const shadow = div.attachShadow({ mode: 'open' }); shadow.innerHTML = 'Some text'; fixtureSetup(div); - var virutalNode = axe._tree[0]; + const virutalNode = axe._tree[0]; // fixture is the outermost element assert.isFalse( @@ -358,51 +442,44 @@ describe('region', function () { ); }); - (shadowSupport.v1 ? it : xit)('should test slotted content', function () { - var div = document.createElement('div'); + it('should test slotted content', () => { + const div = document.createElement('div'); div.innerHTML = 'Some content'; - var shadow = div.attachShadow({ mode: 'open' }); + const shadow = div.attachShadow({ mode: 'open' }); shadow.innerHTML = '
'; - var checkArgs = checkSetup(div); - - assert.isTrue(checkEvaluate.apply(checkContext, checkArgs)); - }); - - (shadowSupport.v1 ? it : xit)( - 'should ignore skiplink targets inside shadow trees', - function () { - var div = document.createElement('div'); - div.innerHTML = - 'skiplink
Content
'; - - var shadow = div.querySelector('div').attachShadow({ mode: 'open' }); - shadow.innerHTML = '
'; - fixtureSetup(div); - var virutalNode = axe.utils.getNodeFromTree(div.querySelector('#target')); - - assert.isFalse( - checkEvaluate.call( - checkContext, - virutalNode.actualNode, - null, - virutalNode - ) - ); - } - ); - - (shadowSupport.v1 ? it : xit)( - 'should find the skiplink in shadow DOM', - function () { - var div = document.createElement('div'); - div.innerHTML = 'Content!'; - var shadow = div.attachShadow({ mode: 'open' }); - shadow.innerHTML = - 'skiplink
'; - var checkArgs = checkSetup(div); - - assert.isTrue(checkEvaluate.apply(checkContext, checkArgs)); - assert.lengthOf(checkContext._relatedNodes, 0); - } - ); + const checkArgs = checkSetup(div); + + assert.isTrue(checkEvaluate.apply(checkContext, checkArgs)); + }); + + it('should ignore skiplink targets inside shadow trees', () => { + const div = document.createElement('div'); + div.innerHTML = 'skiplink
Content
'; + + const shadow = div.querySelector('div').attachShadow({ mode: 'open' }); + shadow.innerHTML = '
'; + fixtureSetup(div); + const virutalNode = axe.utils.getNodeFromTree(div.querySelector('#target')); + + assert.isFalse( + checkEvaluate.call( + checkContext, + virutalNode.actualNode, + null, + virutalNode + ) + ); + }); + + it('should find the skiplink in shadow DOM', () => { + const div = document.createElement('div'); + div.innerHTML = 'Content!'; + const shadow = div.attachShadow({ mode: 'open' }); + shadow.innerHTML = + 'skiplink
'; + const checkArgs = checkSetup(div); + + assert.isTrue(checkEvaluate.apply(checkContext, checkArgs)); + assert.lengthOf(checkContext._relatedNodes, 0); + }); }); diff --git a/test/checks/navigation/skip-link.js b/test/checks/navigation/skip-link.js index c8571c7c4..b6531d761 100644 --- a/test/checks/navigation/skip-link.js +++ b/test/checks/navigation/skip-link.js @@ -1,46 +1,48 @@ -describe('skip-link', function () { - 'use strict'; +describe('skip-link', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); + const fixture = document.getElementById('fixture'); - it('should return true if the href points to an element with an ID', function () { + it('should return true if the href points to an element with an ID', () => { fixture.innerHTML = 'Click Here

Introduction

'; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('a'); + const node = fixture.querySelector('a'); assert.isTrue(axe.testUtils.getCheckEvaluate('skip-link')(node)); }); - it('should return true if the href points to an element with an name', function () { + it('should return true if the href points to an element with an name', () => { fixture.innerHTML = 'Click Here'; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('a'); + const node = fixture.querySelector('a'); assert.isTrue(axe.testUtils.getCheckEvaluate('skip-link')(node)); }); - it('should return false if the href points to a non-existent element', function () { + it('should return false if the href points to a non-existent element', () => { fixture.innerHTML = 'Click Here

Introduction

'; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('a'); + const node = fixture.querySelector('a'); assert.isFalse(axe.testUtils.getCheckEvaluate('skip-link')(node)); }); - it('should return undefined if the target has display:none', function () { - fixture.innerHTML = - 'Click Here' + - '

Introduction

'; + it('should return undefined if the target has display:none', () => { + fixture.innerHTML = html` + Click Here +

Introduction

+ `; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('a'); + const node = fixture.querySelector('a'); assert.isUndefined(axe.testUtils.getCheckEvaluate('skip-link')(node)); }); - it('should return undefined if the target has aria-hidden=true', function () { - fixture.innerHTML = - 'Click Here' + - '

Introduction

'; + it('should return undefined if the target has aria-hidden=true', () => { + fixture.innerHTML = html` + Click Here +

Introduction

+ `; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('a'); + const node = fixture.querySelector('a'); assert.isUndefined(axe.testUtils.getCheckEvaluate('skip-link')(node)); }); }); diff --git a/test/checks/navigation/unique-frame-title-after.js b/test/checks/navigation/unique-frame-title-after.js index 02e6e10a7..81681ee01 100644 --- a/test/checks/navigation/unique-frame-title-after.js +++ b/test/checks/navigation/unique-frame-title-after.js @@ -1,15 +1,13 @@ /*eslint indent: 0*/ -describe('unique-frame-title-after', function () { - 'use strict'; +describe('unique-frame-title-after', () => { + const checkContext = axe.testUtils.MockCheckContext(); - var checkContext = axe.testUtils.MockCheckContext(); - - afterEach(function () { + afterEach(() => { checkContext.reset(); }); - it('should remove any check whose data only appears once', function () { - var result = checks['unique-frame-title'].after([ + it('should remove any check whose data only appears once', () => { + const result = checks['unique-frame-title'].after([ { data: 'bananas' }, diff --git a/test/checks/navigation/unique-frame-title.js b/test/checks/navigation/unique-frame-title.js index ff8beee79..ac759070b 100644 --- a/test/checks/navigation/unique-frame-title.js +++ b/test/checks/navigation/unique-frame-title.js @@ -1,15 +1,13 @@ -describe('unique-frame-title', function () { - 'use strict'; +describe('unique-frame-title', () => { + const checkContext = axe.testUtils.MockCheckContext(); + const queryFixture = axe.testUtils.queryFixture; - var checkContext = axe.testUtils.MockCheckContext(); - var queryFixture = axe.testUtils.queryFixture; - - afterEach(function () { + afterEach(() => { checkContext.reset(); }); - it('should log title to data and return true', function () { - var vNode = queryFixture(''); + it('should log title to data and return true', () => { + const vNode = queryFixture(''); assert.isTrue( axe.testUtils .getCheckEvaluate('unique-frame-title') @@ -18,8 +16,8 @@ describe('unique-frame-title', function () { assert.equal(checkContext._data, 'bananas'); }); - it('should convert text to lower case', function () { - var vNode = queryFixture( + it('should convert text to lower case', () => { + const vNode = queryFixture( '' ); axe.testUtils @@ -28,8 +26,8 @@ describe('unique-frame-title', function () { assert.equal(checkContext._data, 'app le'); }); - it('should take out space differences', function () { - var vNode = queryFixture(''); + it('should take out space differences', () => { + const vNode = queryFixture(''); axe.testUtils .getCheckEvaluate('unique-frame-title') .call(checkContext, null, {}, vNode); diff --git a/test/checks/parser/duplicate-id.js b/test/checks/parser/duplicate-id.js index f48b8e520..caf0fcf8b 100644 --- a/test/checks/parser/duplicate-id.js +++ b/test/checks/parser/duplicate-id.js @@ -1,19 +1,18 @@ -describe('duplicate-id', function () { - 'use strict'; +describe('duplicate-id', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var shadowSupport = axe.testUtils.shadowSupport; + const fixture = document.getElementById('fixture'); - var checkContext = axe.testUtils.MockCheckContext(); + const checkContext = axe.testUtils.MockCheckContext(); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; checkContext.reset(); }); - it('should return true if there is only one element with an ID', function () { + it('should return true if there is only one element with an ID', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.isTrue( axe.testUtils.getCheckEvaluate('duplicate-id').call(checkContext, node) ); @@ -21,9 +20,9 @@ describe('duplicate-id', function () { assert.deepEqual(checkContext._relatedNodes, []); }); - it('should return false if there are multiple elements with an ID', function () { + it('should return false if there are multiple elements with an ID', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.isFalse( axe.testUtils.getCheckEvaluate('duplicate-id').call(checkContext, node) ); @@ -31,7 +30,7 @@ describe('duplicate-id', function () { assert.deepEqual(checkContext._relatedNodes, [node.nextSibling]); }); - it('should return remove duplicates', function () { + it('should return remove duplicates', () => { assert.deepEqual( checks['duplicate-id'].after([ { data: 'a' }, @@ -42,94 +41,86 @@ describe('duplicate-id', function () { ); }); - it('should ignore empty ids', function () { + it('should ignore empty ids', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('[data-testelm="1"]'); + const node = fixture.querySelector('[data-testelm="1"]'); assert.isTrue( axe.testUtils.getCheckEvaluate('duplicate-id').call(checkContext, node) ); }); - it('should allow overwrote ids', function () { - fixture.innerHTML = - '
'; - var node = fixture.querySelector('[data-testelm="1"]'); + it('should allow overwrote ids', () => { + fixture.innerHTML = html` +
+ +
+ `; + const node = fixture.querySelector('[data-testelm="1"]'); + + assert.isTrue( + axe.testUtils.getCheckEvaluate('duplicate-id').call(checkContext, node) + ); + }); + + it('should find duplicate IDs in the same shadow DOM', () => { + const div = document.createElement('div'); + div.id = 'target'; + const shadow = div.attachShadow({ mode: 'open' }); + shadow.innerHTML = '

text

'; + const node = shadow.querySelector('span'); + fixture.appendChild(div); + + assert.isFalse( + axe.testUtils.getCheckEvaluate('duplicate-id').call(checkContext, node) + ); + assert.lengthOf(checkContext._relatedNodes, 1); + assert.deepEqual(checkContext._relatedNodes, [shadow.querySelector('p')]); + }); + + it('should ignore duplicate IDs if they are in different document roots', () => { + const node = document.createElement('div'); + node.id = 'target'; + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = ''; + fixture.appendChild(node); + + assert.isTrue( + axe.testUtils.getCheckEvaluate('duplicate-id').call(checkContext, node) + ); + assert.lengthOf(checkContext._relatedNodes, 0); + }); + + it('should ignore same IDs outside shadow trees', () => { + const div = document.createElement('div'); + div.id = 'target'; + const shadow = div.attachShadow({ mode: 'open' }); + shadow.innerHTML = ''; + const node = shadow.querySelector('#target'); + fixture.appendChild(div); assert.isTrue( axe.testUtils.getCheckEvaluate('duplicate-id').call(checkContext, node) ); + assert.lengthOf(checkContext._relatedNodes, 0); }); - (shadowSupport.v1 ? it : xit)( - 'should find duplicate IDs in the same shadow DOM', - function () { - var div = document.createElement('div'); - div.id = 'target'; - var shadow = div.attachShadow({ mode: 'open' }); - shadow.innerHTML = '

text

'; - var node = shadow.querySelector('span'); - fixture.appendChild(div); - - assert.isFalse( - axe.testUtils.getCheckEvaluate('duplicate-id').call(checkContext, node) - ); - assert.lengthOf(checkContext._relatedNodes, 1); - assert.deepEqual(checkContext._relatedNodes, [shadow.querySelector('p')]); - } - ); - - (shadowSupport.v1 ? it : xit)( - 'should ignore duplicate IDs if they are in different document roots', - function () { - var node = document.createElement('div'); - node.id = 'target'; - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = ''; - fixture.appendChild(node); - - assert.isTrue( - axe.testUtils.getCheckEvaluate('duplicate-id').call(checkContext, node) - ); - assert.lengthOf(checkContext._relatedNodes, 0); - } - ); - - (shadowSupport.v1 ? it : xit)( - 'should ignore same IDs outside shadow trees', - function () { - var div = document.createElement('div'); - div.id = 'target'; - var shadow = div.attachShadow({ mode: 'open' }); - shadow.innerHTML = ''; - var node = shadow.querySelector('#target'); - fixture.appendChild(div); - - assert.isTrue( - axe.testUtils.getCheckEvaluate('duplicate-id').call(checkContext, node) - ); - assert.lengthOf(checkContext._relatedNodes, 0); - } - ); - - (shadowSupport.v1 ? it : xit)( - 'should compare slotted content with the light DOM', - function () { - var node = document.createElement('div'); - node.id = 'target'; - node.innerHTML = '

text

'; - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = ''; - fixture.appendChild(node); - - assert.isFalse( - axe.testUtils.getCheckEvaluate('duplicate-id').call(checkContext, node) - ); - assert.lengthOf(checkContext._relatedNodes, 1); - assert.deepEqual(checkContext._relatedNodes, [node.querySelector('p')]); - } - ); + it('should compare slotted content with the light DOM', () => { + const node = document.createElement('div'); + node.id = 'target'; + node.innerHTML = '

text

'; + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = ''; + fixture.appendChild(node); + + assert.isFalse( + axe.testUtils.getCheckEvaluate('duplicate-id').call(checkContext, node) + ); + assert.lengthOf(checkContext._relatedNodes, 1); + assert.deepEqual(checkContext._relatedNodes, [node.querySelector('p')]); + }); }); diff --git a/test/checks/shared/abstractrole.js b/test/checks/shared/abstractrole.js index 8b0a639dd..b48088e7f 100644 --- a/test/checks/shared/abstractrole.js +++ b/test/checks/shared/abstractrole.js @@ -1,17 +1,15 @@ -describe('abstractrole', function () { - 'use strict'; +describe('abstractrole', () => { + const fixture = document.getElementById('fixture'); + const queryFixture = axe.testUtils.queryFixture; + const checkContext = axe.testUtils.MockCheckContext(); - var fixture = document.getElementById('fixture'); - var queryFixture = axe.testUtils.queryFixture; - var checkContext = axe.testUtils.MockCheckContext(); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; checkContext.reset(); }); - it('should return false if applied to a concrete role', function () { - var virtualNode = queryFixture( + it('should return false if applied to a concrete role', () => { + const virtualNode = queryFixture( '' ); assert.isFalse( @@ -25,8 +23,8 @@ describe('abstractrole', function () { assert.isNull(checkContext._data); }); - it('should return false if applied to a nonsensical role', function () { - var virtualNode = queryFixture( + it('should return false if applied to a nonsensical role', () => { + const virtualNode = queryFixture( '
Contents
' ); assert.isFalse( @@ -40,8 +38,8 @@ describe('abstractrole', function () { assert.isNull(checkContext._data); }); - it('should return true if applied to an abstract role', function () { - var virtualNode = queryFixture( + it('should return true if applied to an abstract role', () => { + const virtualNode = queryFixture( '
Contents
' ); assert.isTrue( @@ -55,8 +53,8 @@ describe('abstractrole', function () { assert.deepEqual(checkContext._data, ['widget']); }); - it('should return false if applied to multiple concrete roles', function () { - var virtualNode = queryFixture( + it('should return false if applied to multiple concrete roles', () => { + const virtualNode = queryFixture( '
Contents
' ); assert.isFalse( @@ -70,8 +68,8 @@ describe('abstractrole', function () { assert.isNull(checkContext._data); }); - it('should return true if applied to at least one abstract role', function () { - var virtualNode = queryFixture( + it('should return true if applied to at least one abstract role', () => { + const virtualNode = queryFixture( '
Contents
' ); assert.isTrue( diff --git a/test/checks/shared/aria-label.js b/test/checks/shared/aria-label.js index 95a0c96e6..4fcf745a4 100644 --- a/test/checks/shared/aria-label.js +++ b/test/checks/shared/aria-label.js @@ -1,36 +1,34 @@ -describe('aria-label', function () { - 'use strict'; +describe('aria-label', () => { + const fixture = document.getElementById('fixture'); + const checkSetup = axe.testUtils.checkSetup; - var fixture = document.getElementById('fixture'); - var checkSetup = axe.testUtils.checkSetup; - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should return true if an aria-label is present', function () { - var checkArgs = checkSetup('
'); + it('should return true if an aria-label is present', () => { + const checkArgs = checkSetup('
'); assert.isTrue( axe.testUtils.getCheckEvaluate('aria-label').apply(null, checkArgs) ); }); - it('should return false if an aria-label is not present', function () { - var checkArgs = checkSetup('
'); + it('should return false if an aria-label is not present', () => { + const checkArgs = checkSetup('
'); assert.isFalse( axe.testUtils.getCheckEvaluate('aria-label').apply(null, checkArgs) ); }); - it('should return false if an aria-label is present, but empty', function () { - var checkArgs = checkSetup('
'); + it('should return false if an aria-label is present, but empty', () => { + const checkArgs = checkSetup('
'); assert.isFalse( axe.testUtils.getCheckEvaluate('aria-label').apply(null, checkArgs) ); }); - it('should collapse whitespace', function () { - var checkArgs = checkSetup( + it('should collapse whitespace', () => { + const checkArgs = checkSetup( '
' ); assert.isFalse( diff --git a/test/checks/shared/avoid-inline-spacing.js b/test/checks/shared/avoid-inline-spacing.js index 87ce60431..892a8c01e 100644 --- a/test/checks/shared/avoid-inline-spacing.js +++ b/test/checks/shared/avoid-inline-spacing.js @@ -1,129 +1,127 @@ -describe('avoid-inline-spacing tests', function () { - 'use strict'; +describe('avoid-inline-spacing tests', () => { + const fixture = document.getElementById('fixture'); + const queryFixture = axe.testUtils.queryFixture; + const checkEvaluate = axe.testUtils.getCheckEvaluate('avoid-inline-spacing'); + const checkContext = axe.testUtils.MockCheckContext(); - var fixture = document.getElementById('fixture'); - var queryFixture = axe.testUtils.queryFixture; - var checkEvaluate = axe.testUtils.getCheckEvaluate('avoid-inline-spacing'); - var checkContext = axe.testUtils.MockCheckContext(); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; checkContext.reset(); }); - it('returns true when no inline spacing styles are specified', function () { - var vNode = queryFixture( + it('returns true when no inline spacing styles are specified', () => { + const vNode = queryFixture( '

The quick brown fox jumped over the lazy dog

' ); - var actual = checkEvaluate.call(checkContext, vNode.actualNode); + const actual = checkEvaluate.call(checkContext, vNode.actualNode); assert.isTrue(actual); assert.isNull(checkContext._data); }); - it('returns true when inline spacing styles has invalid value', function () { - var vNode = queryFixture( + it('returns true when inline spacing styles has invalid value', () => { + const vNode = queryFixture( '

The quick brown fox jumped over the lazy dog

' ); - var actual = checkEvaluate.call(checkContext, vNode.actualNode); + const actual = checkEvaluate.call(checkContext, vNode.actualNode); assert.isTrue(actual); assert.isNull(checkContext._data); }); - it('returns true when inline spacing styles has invalid value and `!important` priority', function () { - var vNode = queryFixture( + it('returns true when inline spacing styles has invalid value and `!important` priority', () => { + const vNode = queryFixture( '

The quick brown fox jumped over the lazy dog

' ); - var actual = checkEvaluate.call(checkContext, vNode.actualNode); + const actual = checkEvaluate.call(checkContext, vNode.actualNode); assert.isTrue(actual); assert.isNull(checkContext._data); }); - it('returns true when `line-height` style specified has no `!important` priority', function () { - var vNode = queryFixture( + it('returns true when `line-height` style specified has no `!important` priority', () => { + const vNode = queryFixture( '

The quick brown fox jumped over the lazy dog

' ); - var actual = checkEvaluate.call(checkContext, vNode.actualNode); + const actual = checkEvaluate.call(checkContext, vNode.actualNode); assert.isTrue(actual); assert.isNull(checkContext._data); }); - it('returns true when `letter-spacing` style specified has no `!important` priority', function () { - var vNode = queryFixture( + it('returns true when `letter-spacing` style specified has no `!important` priority', () => { + const vNode = queryFixture( '

The quick brown fox jumped over the lazy dog

' ); - var actual = checkEvaluate.call(checkContext, vNode.actualNode); + const actual = checkEvaluate.call(checkContext, vNode.actualNode); assert.isTrue(actual); assert.isNull(checkContext._data); }); - it('returns true when `word-spacing` style specified has no `!important` priority', function () { - var vNode = queryFixture( + it('returns true when `word-spacing` style specified has no `!important` priority', () => { + const vNode = queryFixture( '

The quick brown fox jumped over the lazy dog

' ); - var actual = checkEvaluate.call(checkContext, vNode.actualNode); + const actual = checkEvaluate.call(checkContext, vNode.actualNode); assert.isTrue(actual); assert.isNull(checkContext._data); }); - it('returns true when none of the multiple inline spacing styles specified have priority of `!important`', function () { - var vNode = queryFixture( + it('returns true when none of the multiple inline spacing styles specified have priority of `!important`', () => { + const vNode = queryFixture( '

The quick brown fox jumped over the lazy dog

' ); - var actual = checkEvaluate.call(checkContext, vNode.actualNode); + const actual = checkEvaluate.call(checkContext, vNode.actualNode); assert.isTrue(actual); assert.isNull(checkContext._data); }); - it('returns false when `line-height` style specified has `!important` priority', function () { - var vNode = queryFixture( + it('returns false when `line-height` style specified has `!important` priority', () => { + const vNode = queryFixture( '

The quick brown fox jumped over the lazy dog

' ); - var actual = checkEvaluate.call(checkContext, vNode.actualNode); + const actual = checkEvaluate.call(checkContext, vNode.actualNode); assert.isFalse(actual); assert.deepEqual(checkContext._data, ['line-height']); }); - it('returns false when `letter-spacing` style specified has `!important` priority', function () { - var vNode = queryFixture( + it('returns false when `letter-spacing` style specified has `!important` priority', () => { + const vNode = queryFixture( '

The quick brown fox jumped over the lazy dog

' ); - var actual = checkEvaluate.call(checkContext, vNode.actualNode); + const actual = checkEvaluate.call(checkContext, vNode.actualNode); assert.isFalse(actual); assert.deepEqual(checkContext._data, ['letter-spacing']); }); - it('returns false when `word-spacing` style specified has `!important` priority', function () { - var vNode = queryFixture( + it('returns false when `word-spacing` style specified has `!important` priority', () => { + const vNode = queryFixture( '

The quick brown fox jumped over the lazy dog

' ); - var actual = checkEvaluate.call(checkContext, vNode.actualNode); + const actual = checkEvaluate.call(checkContext, vNode.actualNode); assert.isFalse(actual); assert.deepEqual(checkContext._data, ['word-spacing']); }); - it('returns false when any of the multiple inline spacing styles specifies priority of `!important`', function () { - var vNode = queryFixture( + it('returns false when any of the multiple inline spacing styles specifies priority of `!important`', () => { + const vNode = queryFixture( '

The quick brown fox jumped over the lazy dog

' ); - var actual = checkEvaluate.call(checkContext, vNode.actualNode); + const actual = checkEvaluate.call(checkContext, vNode.actualNode); assert.isFalse(actual); assert.deepEqual(checkContext._data, ['letter-spacing']); }); - it('returns false when multiple inline spacing styles specifies priority of `!important`', function () { - var vNode = queryFixture( + it('returns false when multiple inline spacing styles specifies priority of `!important`', () => { + const vNode = queryFixture( '

The quick brown fox jumped over the lazy dog

' ); - var actual = checkEvaluate.call(checkContext, vNode.actualNode); + const actual = checkEvaluate.call(checkContext, vNode.actualNode); assert.isFalse(actual); assert.deepEqual(checkContext._data, ['line-height', 'letter-spacing']); }); - it('supports options.cssProperties', function () { - var vNode = queryFixture( + it('supports options.cssProperties', () => { + const vNode = queryFixture( '

The quick brown fox jumped over the lazy dog

' ); - var actual = checkEvaluate.call(checkContext, vNode.actualNode, { + const actual = checkEvaluate.call(checkContext, vNode.actualNode, { cssProperties: ['font-size'] }); assert.isFalse(actual); diff --git a/test/checks/shared/button-has-visible-text.js b/test/checks/shared/button-has-visible-text.js index 92231549b..898a98ef4 100644 --- a/test/checks/shared/button-has-visible-text.js +++ b/test/checks/shared/button-has-visible-text.js @@ -1,17 +1,15 @@ -describe('button-has-visible-text', function () { - 'use strict'; +describe('button-has-visible-text', () => { + const fixture = document.getElementById('fixture'); + const checkSetup = axe.testUtils.checkSetup; + const checkContext = axe.testUtils.MockCheckContext(); - var fixture = document.getElementById('fixture'); - var checkSetup = axe.testUtils.checkSetup; - var checkContext = axe.testUtils.MockCheckContext(); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; checkContext.reset(); }); - it('should return false if button element is empty', function () { - var checkArgs = checkSetup('', 'button'); + it('should return false if button element is empty', () => { + const checkArgs = checkSetup('', 'button'); assert.isFalse( axe.testUtils @@ -20,8 +18,8 @@ describe('button-has-visible-text', function () { ); }); - it('should return true if a button element has text', function () { - var checkArgs = checkSetup('', 'button'); + it('should return true if a button element has text', () => { + const checkArgs = checkSetup('', 'button'); assert.isTrue( axe.testUtils @@ -30,8 +28,8 @@ describe('button-has-visible-text', function () { ); }); - it('should return true if ARIA button has text', function () { - var checkArgs = checkSetup( + it('should return true if ARIA button has text', () => { + const checkArgs = checkSetup( '
Text
', '[role=button]' ); @@ -43,8 +41,8 @@ describe('button-has-visible-text', function () { ); }); - it('should return false if ARIA button has no text', function () { - var checkArgs = checkSetup('
', '[role=button]'); + it('should return false if ARIA button has no text', () => { + const checkArgs = checkSetup('
', '[role=button]'); assert.isFalse( axe.testUtils @@ -53,9 +51,9 @@ describe('button-has-visible-text', function () { ); }); - describe('SerialVirtualNode', function () { - it('should return incomplete if no children are passed', function () { - var node = new axe.SerialVirtualNode({ + describe('SerialVirtualNode', () => { + it('should return incomplete if no children are passed', () => { + const node = new axe.SerialVirtualNode({ nodeName: 'button' }); @@ -68,8 +66,8 @@ describe('button-has-visible-text', function () { ); }); - it('should return false if button element is empty', function () { - var node = new axe.SerialVirtualNode({ + it('should return false if button element is empty', () => { + const node = new axe.SerialVirtualNode({ nodeName: 'button' }); node.children = []; @@ -83,11 +81,11 @@ describe('button-has-visible-text', function () { ); }); - it('should return true if a button element has text', function () { - var node = new axe.SerialVirtualNode({ + it('should return true if a button element has text', () => { + const node = new axe.SerialVirtualNode({ nodeName: 'button' }); - var child = new axe.SerialVirtualNode({ + const child = new axe.SerialVirtualNode({ nodeName: '#text', nodeType: 3, nodeValue: 'Text' diff --git a/test/checks/shared/doc-has-title.js b/test/checks/shared/doc-has-title.js index 64048d917..468bbe72e 100644 --- a/test/checks/shared/doc-has-title.js +++ b/test/checks/shared/doc-has-title.js @@ -1,28 +1,26 @@ -describe('doc-has-title', function () { - 'use strict'; +describe('doc-has-title', () => { + const fixture = document.getElementById('fixture'); - var fixture = document.getElementById('fixture'); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should return false if title is empty', function () { - var orig = document.title; + it('should return false if title is empty', () => { + const orig = document.title; document.title = ''; assert.isFalse(axe.testUtils.getCheckEvaluate('doc-has-title')(fixture)); document.title = orig; }); - it('should return false if title contains only whitespace', function () { - var orig = document.title; + it('should return false if title contains only whitespace', () => { + const orig = document.title; document.title = ' \t\r\n \n \r \n\t'; assert.isFalse(axe.testUtils.getCheckEvaluate('doc-has-title')(fixture)); document.title = orig; }); - it('should return true if title is non-empty', function () { - var orig = document.title; + it('should return true if title is non-empty', () => { + const orig = document.title; document.title = 'Bananas'; assert.isTrue(axe.testUtils.getCheckEvaluate('doc-has-title')(fixture)); diff --git a/test/checks/shared/exists.js b/test/checks/shared/exists.js index d2b4ad132..2919a54db 100644 --- a/test/checks/shared/exists.js +++ b/test/checks/shared/exists.js @@ -1,7 +1,5 @@ -describe('exists', function () { - 'use strict'; - - it('should return undefined', function () { +describe('exists', () => { + it('should return undefined', () => { assert.isUndefined(checks.exists.evaluate()); }); }); diff --git a/test/checks/shared/has-alt.js b/test/checks/shared/has-alt.js index 4f3c0060b..3d3b5d166 100644 --- a/test/checks/shared/has-alt.js +++ b/test/checks/shared/has-alt.js @@ -1,36 +1,34 @@ -describe('has-alt', function () { - 'use strict'; +describe('has-alt', () => { + const fixture = document.getElementById('fixture'); + const checkSetup = axe.testUtils.checkSetup; - var fixture = document.getElementById('fixture'); - var checkSetup = axe.testUtils.checkSetup; - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should return true if an alt is present', function () { - var checkArgs = checkSetup('woohoo'); + it('should return true if an alt is present', () => { + const checkArgs = checkSetup('woohoo'); assert.isTrue( axe.testUtils.getCheckEvaluate('has-alt').apply(null, checkArgs) ); }); - it('should return true if an empty alt is present', function () { - var checkArgs = checkSetup(''); + it('should return true if an empty alt is present', () => { + const checkArgs = checkSetup(''); assert.isTrue( axe.testUtils.getCheckEvaluate('has-alt').apply(null, checkArgs) ); }); - it('should return true if a null alt is present', function () { - var checkArgs = checkSetup(''); + it('should return true if a null alt is present', () => { + const checkArgs = checkSetup(''); assert.isTrue( axe.testUtils.getCheckEvaluate('has-alt').apply(null, checkArgs) ); }); - it('should return false if an alt is not present', function () { - var checkArgs = checkSetup(''); + it('should return false if an alt is not present', () => { + const checkArgs = checkSetup(''); assert.isFalse( axe.testUtils.getCheckEvaluate('has-alt').apply(null, checkArgs) ); diff --git a/test/checks/shared/has-visible-text.js b/test/checks/shared/has-visible-text.js index fedca81cb..4e665a5ca 100644 --- a/test/checks/shared/has-visible-text.js +++ b/test/checks/shared/has-visible-text.js @@ -1,15 +1,13 @@ -describe('has-visible-text', function () { - 'use strict'; +describe('has-visible-text', () => { + const checkSetup = axe.testUtils.checkSetup; + const checkContext = axe.testUtils.MockCheckContext(); - var checkSetup = axe.testUtils.checkSetup; - var checkContext = axe.testUtils.MockCheckContext(); - - afterEach(function () { + afterEach(() => { checkContext.reset(); }); - it('should return false if there is no visible text', function () { - var params = checkSetup('

'); + it('should return false if there is no visible text', () => { + const params = checkSetup('

'); assert.isFalse( axe.testUtils .getCheckEvaluate('has-visible-text') @@ -17,8 +15,8 @@ describe('has-visible-text', function () { ); }); - it('should return false if there is text, but its hidden', function () { - var params = checkSetup( + it('should return false if there is text, but its hidden', () => { + const params = checkSetup( '

hello!

' ); assert.isFalse( @@ -28,8 +26,8 @@ describe('has-visible-text', function () { ); }); - it('should return true if there is visible text', function () { - var params = checkSetup('

hello!

'); + it('should return true if there is visible text', () => { + const params = checkSetup('

hello!

'); assert.isTrue( axe.testUtils .getCheckEvaluate('has-visible-text') @@ -37,9 +35,9 @@ describe('has-visible-text', function () { ); }); - describe('SerialVirtualNode', function () { - it('should return false if element is not named from contents', function () { - var node = new axe.SerialVirtualNode({ + describe('SerialVirtualNode', () => { + it('should return false if element is not named from contents', () => { + const node = new axe.SerialVirtualNode({ nodeName: 'article' }); @@ -48,8 +46,8 @@ describe('has-visible-text', function () { ); }); - it('should return incomplete if no other properties are set', function () { - var node = new axe.SerialVirtualNode({ + it('should return incomplete if no other properties are set', () => { + const node = new axe.SerialVirtualNode({ nodeName: 'button' }); @@ -58,8 +56,8 @@ describe('has-visible-text', function () { ); }); - it('should return false if there is no visible text', function () { - var node = new axe.SerialVirtualNode({ + it('should return false if there is no visible text', () => { + const node = new axe.SerialVirtualNode({ nodeName: 'button' }); node.children = []; @@ -69,11 +67,11 @@ describe('has-visible-text', function () { ); }); - it('should return true if there is visible text', function () { - var node = new axe.SerialVirtualNode({ + it('should return true if there is visible text', () => { + const node = new axe.SerialVirtualNode({ nodeName: 'p' }); - var child = new axe.SerialVirtualNode({ + const child = new axe.SerialVirtualNode({ nodeName: '#text', nodeType: 3, nodeValue: 'hello!' diff --git a/test/checks/shared/inline-style-property.js b/test/checks/shared/inline-style-property.js index 9d93d30f0..f1c1da75a 100644 --- a/test/checks/shared/inline-style-property.js +++ b/test/checks/shared/inline-style-property.js @@ -1,4 +1,5 @@ describe('inline-style-property tests', () => { + const html = axe.testUtils.html; const fixture = document.getElementById('fixture'); const checkSetup = axe.testUtils.checkSetup; @@ -64,7 +65,7 @@ describe('inline-style-property tests', () => { 'letter-spacing: 0.2em' ].join('; '); const params = checkSetup( - '

Hello world

' + html`

Hello world

` ); const result = checkEvaluate.apply(checkContext, params); assert.isFalse(result); @@ -126,10 +127,10 @@ describe('inline-style-property tests', () => { }); it('is true when `inherited` is used along with !important', () => { - const params = checkSetup( - '

' + - 'Hello world' - ); + const params = checkSetup(html` +

+ Hello world + `); const result = checkEvaluate.apply(checkContext, params); assert.isTrue(result); assert.deepEqual(checkContext._data, { @@ -139,10 +140,10 @@ describe('inline-style-property tests', () => { }); it('is true when `unset` is used along with !important', () => { - const params = checkSetup( - '

' + - 'Hello world' - ); + const params = checkSetup(html` +

+ Hello world + `); const result = checkEvaluate.apply(checkContext, params); assert.isTrue(result); assert.deepEqual(checkContext._data, { @@ -152,10 +153,10 @@ describe('inline-style-property tests', () => { }); it('is true when `revert` is used along with !important', () => { - const params = checkSetup( - '

' + - 'Hello world' - ); + const params = checkSetup(html` +

+ Hello world + `); const result = checkEvaluate.apply(checkContext, params); assert.isTrue(result); assert.deepEqual(checkContext._data, { @@ -165,10 +166,10 @@ describe('inline-style-property tests', () => { }); it('is true when `revert-layer` is used along with !important', () => { - const params = checkSetup( - '

' + - 'Hello world' - ); + const params = checkSetup(html` +

+ Hello world + `); const result = checkEvaluate.apply(checkContext, params); assert.isTrue(result); assert.deepEqual(checkContext._data, { @@ -250,22 +251,22 @@ describe('inline-style-property tests', () => { }); it('is true when below 1.5em and not !important', () => { - const params = checkSetup( - '

' + - ' The toy brought back fond memories of being lost in the rain forest.' + - '

' - ); + const params = checkSetup(html` +

+ The toy brought back fond memories of being lost in the rain forest. +

+ `); const result = checkEvaluate.apply(checkContext, params); assert.isTrue(result); assert.isNull(checkContext._data); }); it('is false when below 1.5em and !important', () => { - const params = checkSetup( - '

' + - ' The toy brought back fond memories of being lost in the rain forest.' + - '

' - ); + const params = checkSetup(html` +

+ The toy brought back fond memories of being lost in the rain forest. +

+ `); const result = checkEvaluate.apply(checkContext, params); assert.isFalse(result); assert.deepEqual(checkContext._data, { @@ -275,11 +276,11 @@ describe('inline-style-property tests', () => { }); it('is true when 1.5em and !important', () => { - const params = checkSetup( - '

' + - ' The toy brought back fond memories of being lost in the rain forest.' + - '

' - ); + const params = checkSetup(html` +

+ The toy brought back fond memories of being lost in the rain forest. +

+ `); const result = checkEvaluate.apply(checkContext, params); assert.isTrue(result); assert.deepEqual(checkContext._data, { @@ -289,11 +290,14 @@ describe('inline-style-property tests', () => { }); it('returns the 1em for `normal !important`', () => { - const params = checkSetup( - '

' + - ' The toy brought back fond memories of being lost in the rain forest.' + - '

' - ); + const params = checkSetup(html` +

+ The toy brought back fond memories of being lost in the rain forest. +

+ `); const result = checkEvaluate.apply(checkContext, params); assert.isFalse(result); assert.deepEqual(checkContext._data, { @@ -303,11 +307,11 @@ describe('inline-style-property tests', () => { }); it('is true for single line texts', () => { - const params = checkSetup( - '

' + - ' Short' + - '

' - ); + const params = checkSetup(html` +

+ Short +

+ `); const result = checkEvaluate.apply(checkContext, params); assert.isTrue(result); assert.isNull(checkContext._data); diff --git a/test/checks/shared/invalidrole.js b/test/checks/shared/invalidrole.js index d9aa0a394..240307391 100644 --- a/test/checks/shared/invalidrole.js +++ b/test/checks/shared/invalidrole.js @@ -1,17 +1,15 @@ -describe('invalidrole', function () { - 'use strict'; +describe('invalidrole', () => { + const fixture = document.getElementById('fixture'); + const queryFixture = axe.testUtils.queryFixture; + const checkContext = axe.testUtils.MockCheckContext(); - var fixture = document.getElementById('fixture'); - var queryFixture = axe.testUtils.queryFixture; - var checkContext = axe.testUtils.MockCheckContext(); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; checkContext.reset(); }); - it('should return true if applied to an empty role', function () { - var virtualNode = queryFixture('
Contents
'); + it('should return true if applied to an empty role', () => { + const virtualNode = queryFixture('
Contents
'); assert.isTrue( checks.invalidrole.evaluate.call( checkContext, @@ -23,8 +21,8 @@ describe('invalidrole', function () { assert.deepEqual(checkContext._data, ['']); }); - it('should return true if applied to a nonsensical role', function () { - var virtualNode = queryFixture( + it('should return true if applied to a nonsensical role', () => { + const virtualNode = queryFixture( '
Contents
' ); assert.isTrue( @@ -38,8 +36,8 @@ describe('invalidrole', function () { assert.deepEqual(checkContext._data, ['foo']); }); - it('should return false if applied to a concrete role', function () { - var virtualNode = queryFixture( + it('should return false if applied to a concrete role', () => { + const virtualNode = queryFixture( '' ); assert.isFalse( @@ -53,8 +51,8 @@ describe('invalidrole', function () { assert.isNull(checkContext._data); }); - it('should return false if applied to an abstract role', function () { - var virtualNode = queryFixture( + it('should return false if applied to an abstract role', () => { + const virtualNode = queryFixture( '
Contents
' ); assert.isFalse( @@ -68,8 +66,8 @@ describe('invalidrole', function () { assert.isNull(checkContext._data); }); - it('should return false if applied to multiple valid roles', function () { - var virtualNode = queryFixture( + it('should return false if applied to multiple valid roles', () => { + const virtualNode = queryFixture( '
Contents
' ); assert.isFalse( @@ -83,8 +81,8 @@ describe('invalidrole', function () { assert.isNull(checkContext._data); }); - it('should return false if atleast one role is valid', function () { - var virtualNode = queryFixture( + it('should return false if atleast one role is valid', () => { + const virtualNode = queryFixture( '
Contents
' ); assert.isFalse( @@ -97,8 +95,8 @@ describe('invalidrole', function () { ); }); - it('should return true if all roles are invalid', function () { - var virtualNode = queryFixture( + it('should return true if all roles are invalid', () => { + const virtualNode = queryFixture( '
Contents
' ); assert.isTrue( @@ -111,4 +109,33 @@ describe('invalidrole', function () { ); assert.deepEqual(checkContext._data, ['foo', 'bar']); }); + + it('should return true if applied to an uppercase nonsensical role', () => { + const virtualNode = queryFixture( + '
Contents
' + ); + assert.isTrue( + checks.invalidrole.evaluate.call( + checkContext, + virtualNode.actualNode, + null, + virtualNode + ) + ); + assert.deepEqual(checkContext._data, ['FOO']); + }); + + it('should return false if applied to an uppercase valid role', () => { + const virtualNode = queryFixture( + '
Contents
' + ); + assert.isFalse( + checks.invalidrole.evaluate.call( + checkContext, + virtualNode.actualNode, + null, + virtualNode + ) + ); + }); }); diff --git a/test/checks/shared/is-on-screen.js b/test/checks/shared/is-on-screen.js index e82cb70de..de9804a1f 100644 --- a/test/checks/shared/is-on-screen.js +++ b/test/checks/shared/is-on-screen.js @@ -1,28 +1,28 @@ -describe('is-on-screen', function () { - 'use strict'; +describe('is-on-screen', () => { + const queryFixture = axe.testUtils.queryFixture; - var queryFixture = axe.testUtils.queryFixture; - - it('should return true for visible elements', function () { - var vNode = queryFixture('
elm
'); + it('should return true for visible elements', () => { + const vNode = queryFixture('
elm
'); assert.isTrue(axe.testUtils.getCheckEvaluate('is-on-screen')(vNode)); }); - it('should return true for aria-hidden=true elements', function () { - var vNode = queryFixture(''); + it('should return true for aria-hidden=true elements', () => { + const vNode = queryFixture(''); assert.isTrue(axe.testUtils.getCheckEvaluate('is-on-screen')(vNode)); }); - it('should return false for display:none elements', function () { - var vNode = queryFixture(''); + it('should return false for display:none elements', () => { + const vNode = queryFixture( + '' + ); assert.isFalse(axe.testUtils.getCheckEvaluate('is-on-screen')(vNode)); }); - it('should return false for off screen elements', function () { - var vNode = queryFixture( + it('should return false for off screen elements', () => { + const vNode = queryFixture( '
elm
' ); diff --git a/test/checks/shared/non-empty-alt.js b/test/checks/shared/non-empty-alt.js index cf5fe8f38..94f6a3637 100644 --- a/test/checks/shared/non-empty-alt.js +++ b/test/checks/shared/non-empty-alt.js @@ -1,35 +1,35 @@ -describe('non-empty-alt', function () { - 'use strict'; +describe('non-empty-alt', () => { + const fixture = document.getElementById('fixture'); + const checkSetup = axe.testUtils.checkSetup; + const checkEvaluate = axe.testUtils.getCheckEvaluate('non-empty-alt'); + const checkContext = axe.testUtils.MockCheckContext(); - var fixture = document.getElementById('fixture'); - var checkSetup = axe.testUtils.checkSetup; - var checkEvaluate = axe.testUtils.getCheckEvaluate('non-empty-alt'); - var checkContext = axe.testUtils.MockCheckContext(); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; checkContext.reset(); }); - it('should return true if an alt is present', function () { - var params = checkSetup('woohoo'); + it('should return true if an alt is present', () => { + const params = checkSetup('woohoo'); assert.isTrue(checkEvaluate.apply(checkContext, params)); }); - it('should return false if an alt is not present', function () { - var params = checkSetup(''); + it('should return false if an alt is not present', () => { + const params = checkSetup(''); assert.isFalse(checkEvaluate.apply(checkContext, params)); assert.equal(checkContext._data.messageKey, 'noAttr'); }); - it('should return false if an alt is present, but empty', function () { - var params = checkSetup(' '); + it('should return false if an alt is present, but empty', () => { + const params = checkSetup(' '); assert.isFalse(checkEvaluate.apply(checkContext, params)); assert.equal(checkContext._data.messageKey, 'emptyAttr'); }); - it('should collapse whitespace', function () { - var params = checkSetup(' \t \n \r \t  \t\r\n '); + it('should collapse whitespace', () => { + const params = checkSetup( + ' \t \n \r \t  \t\r\n ' + ); assert.isFalse(checkEvaluate.apply(checkContext, params)); assert.equal(checkContext._data.messageKey, 'emptyAttr'); }); diff --git a/test/checks/shared/non-empty-if-present.js b/test/checks/shared/non-empty-if-present.js index 7d25836d1..f469fa496 100644 --- a/test/checks/shared/non-empty-if-present.js +++ b/test/checks/shared/non-empty-if-present.js @@ -1,23 +1,21 @@ -describe('non-empty-if-present', function () { - 'use strict'; - - var fixture = document.getElementById('fixture'); +describe('non-empty-if-present', () => { + const fixture = document.getElementById('fixture'); // These defaults are only available in IE and Edge - var input = document.createElement('input'); + const input = document.createElement('input'); input.type = 'submit'; - var isEdgeOrIe = typeof input.getAttribute('value') === 'string'; + const isEdgeOrIe = typeof input.getAttribute('value') === 'string'; - var checkContext = axe.testUtils.MockCheckContext(); - var queryFixture = axe.testUtils.queryFixture; + const checkContext = axe.testUtils.MockCheckContext(); + const queryFixture = axe.testUtils.queryFixture; - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; checkContext.reset(); }); - it('should return false if a value is present', function () { - var vNode = queryFixture( + it('should return false if a value is present', () => { + const vNode = queryFixture( '' ); @@ -31,8 +29,8 @@ describe('non-empty-if-present', function () { (isEdgeOrIe ? xit : it)( 'should return true if a value is not present', - function () { - var vNode = queryFixture(''); + () => { + const vNode = queryFixture(''); assert.isTrue( axe.testUtils @@ -43,8 +41,8 @@ describe('non-empty-if-present', function () { } ); - it('should return false if an value is present, but empty', function () { - var vNode = queryFixture(''); + it('should return false if an value is present, but empty', () => { + const vNode = queryFixture(''); assert.isFalse( axe.testUtils @@ -53,22 +51,22 @@ describe('non-empty-if-present', function () { ); }); - it('should return false if the element is not a submit or reset input', function () { - var vNode = queryFixture(''); + it('should return false if the element is not a submit or reset input', () => { + let vNode = queryFixture(''); assert.isFalse( axe.testUtils .getCheckEvaluate('non-empty-if-present') .call(checkContext, null, {}, vNode) ); - var vNode = queryFixture(''); + vNode = queryFixture(''); assert.isFalse( axe.testUtils .getCheckEvaluate('non-empty-if-present') .call(checkContext, null, {}, vNode) ); - var vNode = queryFixture('' ); @@ -46,8 +44,8 @@ describe('presentational-role', function () { assert.deepEqual(checkContext._data.messageKey, 'focusable'); }); - it('should return false when the element has global aria attributes', function () { - var vNode = queryFixture( + it('should return false when the element has global aria attributes', () => { + const vNode = queryFixture( '' ); @@ -55,8 +53,8 @@ describe('presentational-role', function () { assert.deepEqual(checkContext._data.messageKey, 'globalAria'); }); - it('should return false when the element has global aria attributes and is focusable', function () { - var vNode = queryFixture( + it('should return false when the element has global aria attributes and is focusable', () => { + const vNode = queryFixture( '' ); @@ -64,8 +62,8 @@ describe('presentational-role', function () { assert.deepEqual(checkContext._data.messageKey, 'both'); }); - it('should return false for iframe element with role=none and title', function () { - var vNode = queryFixture( + it('should return false for iframe element with role=none and title', () => { + const vNode = queryFixture( '' ); @@ -76,8 +74,8 @@ describe('presentational-role', function () { }); }); - it('should return false for iframe element with role=presentation and title', function () { - var vNode = queryFixture( + it('should return false for iframe element with role=presentation and title', () => { + const vNode = queryFixture( '' ); diff --git a/test/checks/shared/role-none.js b/test/checks/shared/role-none.js index aaff4011c..06a24fd0b 100644 --- a/test/checks/shared/role-none.js +++ b/test/checks/shared/role-none.js @@ -1,28 +1,26 @@ -describe('role-none', function () { - 'use strict'; +describe('role-none', () => { + const fixture = document.getElementById('fixture'); + const queryFixture = axe.testUtils.queryFixture; + const checkEvaluate = axe.testUtils.getCheckEvaluate('role-none'); - var fixture = document.getElementById('fixture'); - var queryFixture = axe.testUtils.queryFixture; - var checkEvaluate = axe.testUtils.getCheckEvaluate('role-none'); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should detect role="none" on the element', function () { - var vNode = queryFixture('
'); + it('should detect role="none" on the element', () => { + const vNode = queryFixture('
'); assert.isTrue(checkEvaluate(null, null, vNode)); }); - it('should return false when role !== none', function () { - var vNode = queryFixture('
'); + it('should return false when role !== none', () => { + const vNode = queryFixture('
'); assert.isFalse(checkEvaluate(null, null, vNode)); }); - it('should return false when there is no role attribute', function () { - var vNode = queryFixture('
'); + it('should return false when there is no role attribute', () => { + const vNode = queryFixture('
'); assert.isFalse(checkEvaluate(null, null, vNode)); }); diff --git a/test/checks/shared/role-presentation.js b/test/checks/shared/role-presentation.js index bc779b42d..ab08d5dd3 100644 --- a/test/checks/shared/role-presentation.js +++ b/test/checks/shared/role-presentation.js @@ -1,28 +1,26 @@ -describe('role-presentation', function () { - 'use strict'; +describe('role-presentation', () => { + const fixture = document.getElementById('fixture'); + const queryFixture = axe.testUtils.queryFixture; + const checkEvaluate = axe.testUtils.getCheckEvaluate('role-presentation'); - var fixture = document.getElementById('fixture'); - var queryFixture = axe.testUtils.queryFixture; - var checkEvaluate = axe.testUtils.getCheckEvaluate('role-presentation'); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should detect role="presentation" on the element', function () { - var vNode = queryFixture(''); + it('should detect role="presentation" on the element', () => { + const vNode = queryFixture(''); assert.isTrue(checkEvaluate(null, null, vNode)); }); - it('should return false when role !== presentation', function () { - var vNode = queryFixture('
'); + it('should return false when role !== presentation', () => { + const vNode = queryFixture('
'); assert.isFalse(checkEvaluate(null, null, vNode)); }); - it('should return false when there is no role attribute', function () { - var vNode = queryFixture('
'); + it('should return false when there is no role attribute', () => { + const vNode = queryFixture('
'); assert.isFalse(checkEvaluate(null, null, vNode)); }); diff --git a/test/checks/shared/svg-non-empty-title.js b/test/checks/shared/svg-non-empty-title.js index 4ed56ac21..59b490681 100644 --- a/test/checks/shared/svg-non-empty-title.js +++ b/test/checks/shared/svg-non-empty-title.js @@ -1,82 +1,80 @@ -describe('svg-non-empty-title tests', function () { - 'use strict'; +describe('svg-non-empty-title tests', () => { + const fixture = document.getElementById('fixture'); + const checkContext = axe.testUtils.MockCheckContext(); + const checkSetup = axe.testUtils.checkSetup; + const checkEvaluate = axe.testUtils.getCheckEvaluate('svg-non-empty-title'); - var fixture = document.getElementById('fixture'); - var checkContext = axe.testUtils.MockCheckContext(); - var checkSetup = axe.testUtils.checkSetup; - var checkEvaluate = axe.testUtils.getCheckEvaluate('svg-non-empty-title'); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; checkContext.reset(); }); - it('returns true if the element has a `title` child', function () { - var checkArgs = checkSetup( + it('returns true if the element has a `title` child', () => { + const checkArgs = checkSetup( 'Time II: Party' ); assert.isTrue(checkEvaluate.apply(checkContext, checkArgs)); }); - it('returns true if the `title` child has text nested in another element', function () { - var checkArgs = checkSetup( + it('returns true if the `title` child has text nested in another element', () => { + const checkArgs = checkSetup( 'Time II: Party' ); assert.isTrue(checkEvaluate.apply(checkContext, checkArgs)); }); - it('returns true if the element has a `title` child with `display:none`', function () { - var checkArgs = checkSetup( + it('returns true if the element has a `title` child with `display:none`', () => { + const checkArgs = checkSetup( 'Time II: Party' ); assert.isTrue(checkEvaluate.apply(checkContext, checkArgs)); }); - it('returns false if the element has no `title` child', function () { - var checkArgs = checkSetup(''); + it('returns false if the element has no `title` child', () => { + const checkArgs = checkSetup(''); assert.isFalse(checkEvaluate.apply(checkContext, checkArgs)); assert.equal(checkContext._data.messageKey, 'noTitle'); }); - it('returns false if the `title` child is empty', function () { - var checkArgs = checkSetup(''); + it('returns false if the `title` child is empty', () => { + const checkArgs = checkSetup(''); assert.isFalse(checkEvaluate.apply(checkContext, checkArgs)); assert.equal(checkContext._data.messageKey, 'emptyTitle'); }); - it('returns false if the `title` is a grandchild', function () { - var checkArgs = checkSetup( + it('returns false if the `title` is a grandchild', () => { + const checkArgs = checkSetup( 'Time II: Party' ); assert.isFalse(checkEvaluate.apply(checkContext, checkArgs)); assert.equal(checkContext._data.messageKey, 'noTitle'); }); - it('returns false if the `title` child has only whitespace', function () { - var checkArgs = checkSetup( + it('returns false if the `title` child has only whitespace', () => { + const checkArgs = checkSetup( ' \t\r\n ' ); assert.isFalse(checkEvaluate.apply(checkContext, checkArgs)); assert.equal(checkContext._data.messageKey, 'emptyTitle'); }); - it('returns false if there are multiple titles, and the first is empty', function () { - var checkArgs = checkSetup( + it('returns false if there are multiple titles, and the first is empty', () => { + const checkArgs = checkSetup( 'Time II: Party' ); assert.isFalse(checkEvaluate.apply(checkContext, checkArgs)); assert.equal(checkContext._data.messageKey, 'emptyTitle'); }); - describe('Serial Virtual Node', function () { - it('returns true if the element has a `title` child', function () { - var serialNode = new axe.SerialVirtualNode({ + describe('Serial Virtual Node', () => { + it('returns true if the element has a `title` child', () => { + const serialNode = new axe.SerialVirtualNode({ nodeName: 'svg' }); - var child = new axe.SerialVirtualNode({ + const child = new axe.SerialVirtualNode({ nodeName: 'title' }); - var text = new axe.SerialVirtualNode({ + const text = new axe.SerialVirtualNode({ nodeName: '#text', nodeType: 3, nodeValue: 'Time II: Party' @@ -84,27 +82,27 @@ describe('svg-non-empty-title tests', function () { child.parent = serialNode; child.children = [text]; serialNode.children = [child]; - var checkArgs = [null, {}, serialNode]; + const checkArgs = [null, {}, serialNode]; assert.isTrue(checkEvaluate.apply(checkContext, checkArgs)); }); - it('returns false if the element has no `title` child', function () { - var serialNode = new axe.SerialVirtualNode({ + it('returns false if the element has no `title` child', () => { + const serialNode = new axe.SerialVirtualNode({ nodeName: 'svg' }); serialNode.children = []; - var checkArgs = [null, {}, serialNode]; + const checkArgs = [null, {}, serialNode]; assert.isFalse(checkEvaluate.apply(checkContext, checkArgs)); assert.equal(checkContext._data.messageKey, 'noTitle'); }); - it('returns undefined if the element has empty children', function () { - var serialNode = new axe.SerialVirtualNode({ + it('returns undefined if the element has empty children', () => { + const serialNode = new axe.SerialVirtualNode({ nodeName: 'svg' }); - var checkArgs = [null, {}, serialNode]; + const checkArgs = [null, {}, serialNode]; assert.isUndefined(checkEvaluate.apply(checkContext, checkArgs)); }); diff --git a/test/checks/tables/caption-faked.js b/test/checks/tables/caption-faked.js index b96fbd961..e7882b0b4 100644 --- a/test/checks/tables/caption-faked.js +++ b/test/checks/tables/caption-faked.js @@ -1,80 +1,112 @@ -describe('caption-faked', function () { - 'use strict'; +describe('caption-faked', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); + const fixture = document.getElementById('fixture'); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - var captionFaked; - beforeEach(function () { + let captionFaked; + beforeEach(() => { captionFaked = checks['caption-faked']; }); - it('returns true if the first row has multiple cells', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
'; + it('returns true if the first row has multiple cells', () => { + fixture.innerHTML = html` + + + + + + + + + +
+ `; - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue(captionFaked.evaluate(node)); }); - it('returns true if the table has only one column', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
'; + it('returns true if the table has only one column', () => { + fixture.innerHTML = html` + + + + + + + +
+ `; - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue(captionFaked.evaluate(node)); }); - it('returns true if the table has only one ', function () { - fixture.innerHTML = - '' + - // Accessibility: Expect the unexpected - ' ' + - '
'; + it('returns true if the table has only one ', () => { + fixture.innerHTML = html` + + + + +
+ `; - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue(captionFaked.evaluate(node)); }); - it('returns true if the first column does not span the entire table', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
'; + it('returns true if the first column does not span the entire table', () => { + fixture.innerHTML = html` + + + + + + + + +
+ `; - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue(captionFaked.evaluate(node)); }); - it('returns false if the first is only a single td', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
'; + it('returns false if the first is only a single td', () => { + fixture.innerHTML = html` + + + + + + + + +
+ `; - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isFalse(captionFaked.evaluate(node)); }); - it('returns false if the first is only a single th', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
'; + it('returns false if the first is only a single th', () => { + fixture.innerHTML = html` + + + + + + + + +
+ `; - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isFalse(captionFaked.evaluate(node)); }); }); diff --git a/test/checks/tables/html5-scope.js b/test/checks/tables/html5-scope.js index 3a0ff265f..27d4ffdef 100644 --- a/test/checks/tables/html5-scope.js +++ b/test/checks/tables/html5-scope.js @@ -1,30 +1,28 @@ -describe('html5-scope', function () { - 'use strict'; +describe('html5-scope', () => { + const fixture = document.getElementById('fixture'); - var fixture = document.getElementById('fixture'); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should return true on THs', function () { + it('should return true on THs', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('th'); + const node = fixture.querySelector('th'); assert.isTrue(axe.testUtils.getCheckEvaluate('html5-scope')(node)); }); - it('should return false on TDs', function () { + it('should return false on TDs', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('td'); + const node = fixture.querySelector('td'); assert.isFalse(axe.testUtils.getCheckEvaluate('html5-scope')(node)); }); - it('should return true on non-HTML5 documents', function () { - var origPublicId = document.publicId; + it('should return true on non-HTML5 documents', () => { + const origPublicId = document.publicId; fixture.innerHTML = '
'; - var node = fixture.querySelector('th'); + const node = fixture.querySelector('th'); assert.isTrue(axe.testUtils.getCheckEvaluate('html5-scope')(node)); document.publicId = origPublicId; diff --git a/test/checks/tables/same-caption-summary.js b/test/checks/tables/same-caption-summary.js index 495c4d364..cb9ad978e 100644 --- a/test/checks/tables/same-caption-summary.js +++ b/test/checks/tables/same-caption-summary.js @@ -1,19 +1,18 @@ -describe('same-caption-summary', function () { - 'use strict'; +describe('same-caption-summary', () => { + const html = axe.testUtils.html; - var checkSetup = axe.testUtils.checkSetup; - var shadowCheckSetup = axe.testUtils.shadowCheckSetup; - var shadowSupport = axe.testUtils.shadowSupport; + const checkSetup = axe.testUtils.checkSetup; + const shadowCheckSetup = axe.testUtils.shadowCheckSetup; - var checkContext = axe.testUtils.MockCheckContext(); + const checkContext = axe.testUtils.MockCheckContext(); - afterEach(function () { + afterEach(() => { checkContext.reset(); axe._tree = undefined; }); - it('should return false there is no caption', function () { - var params = checkSetup( + it('should return false there is no caption', () => { + const params = checkSetup( '
' ); @@ -24,8 +23,8 @@ describe('same-caption-summary', function () { ); }); - it('should return false there is no summary', function () { - var params = checkSetup( + it('should return false there is no summary', () => { + const params = checkSetup( '
Hi
' ); @@ -36,8 +35,8 @@ describe('same-caption-summary', function () { ); }); - it('should return false if summary and caption are different', function () { - var params = checkSetup( + it('should return false if summary and caption are different', () => { + const params = checkSetup( '
Hi
' ); @@ -48,8 +47,8 @@ describe('same-caption-summary', function () { ); }); - it('should return true if summary and caption are the same', function () { - var params = checkSetup( + it('should return true if summary and caption are the same', () => { + const params = checkSetup( '
Hi
' ); @@ -60,18 +59,24 @@ describe('same-caption-summary', function () { ); }); - it('should return true if summary and caption are the same with mixed casing', function () { - var params = checkSetup( - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
my table
Head
Data
' - ); + it('should return true if summary and caption are the same with mixed casing', () => { + const params = checkSetup(html` + + + + + + + + + + + + +
+ my table +
Head
Data
+ `); assert.isTrue( axe.testUtils @@ -80,26 +85,32 @@ describe('same-caption-summary', function () { ); }); - (shadowSupport.v1 ? it : xit)( - 'should match slotted caption elements', - function () { - var params = shadowCheckSetup( - '
' + - 'Caption' + - 'Data element 1' + - 'Data element 2' + - '
', - '' + - '' + - '' + - '
' - ); + it('should match slotted caption elements', () => { + const params = shadowCheckSetup( + html` +
+ Caption + Data element 1 + Data element 2 +
+ `, + html` + + + + + + +
+ +
+ ` + ); - assert.isTrue( - axe.testUtils - .getCheckEvaluate('same-caption-summary') - .apply(checkContext, params) - ); - } - ); + assert.isTrue( + axe.testUtils + .getCheckEvaluate('same-caption-summary') + .apply(checkContext, params) + ); + }); }); diff --git a/test/checks/tables/scope-value.js b/test/checks/tables/scope-value.js index a7a21b57b..04f5c3a3c 100644 --- a/test/checks/tables/scope-value.js +++ b/test/checks/tables/scope-value.js @@ -1,38 +1,36 @@ -describe('scope-value', function () { - 'use strict'; +describe('scope-value', () => { + const fixture = document.getElementById('fixture'); - var fixture = document.getElementById('fixture'); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should return true if scope is "col"', function () { + it('should return true if scope is "col"', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('td'); + const node = fixture.querySelector('td'); assert.isTrue(axe.testUtils.getCheckEvaluate('scope-value')(node)); }); - it('should return true if scope is "row"', function () { + it('should return true if scope is "row"', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('td'); + const node = fixture.querySelector('td'); assert.isTrue(axe.testUtils.getCheckEvaluate('scope-value')(node)); }); - it('should return false otherwise', function () { + it('should return false otherwise', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('td'); + const node = fixture.querySelector('td'); assert.isFalse(axe.testUtils.getCheckEvaluate('scope-value')(node)); }); - it('should support options.values', function () { + it('should support options.values', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('td'); + const node = fixture.querySelector('td'); assert.isTrue( axe.testUtils.getCheckEvaluate('scope-value')(node, { diff --git a/test/checks/tables/td-has-header.js b/test/checks/tables/td-has-header.js index 5e6e6cf9c..33e11eeb4 100644 --- a/test/checks/tables/td-has-header.js +++ b/test/checks/tables/td-has-header.js @@ -1,45 +1,46 @@ -describe('td-has-header', function () { - 'use strict'; +describe('td-has-header', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var shadowSupport = axe.testUtils.shadowSupport.v1; - var checkContext = axe.testUtils.MockCheckContext(); + const fixture = document.getElementById('fixture'); + const shadowSupport = axe.testUtils.shadowSupport.v1; + const checkContext = axe.testUtils.MockCheckContext(); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; checkContext.reset(); axe._tree = null; }); - it('should not be fooled by rowspan and colspan', function () { - fixture.innerHTML = - '' + - '' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - '
SpeciesInfo
NameAge
GorillaKoko44
HumanMatt33
'; + it('should not be fooled by rowspan and colspan', () => { + fixture.innerHTML = html` + + + + + + + + + + + + + + + + + + + + + + + +
SpeciesInfo
NameAge
GorillaKoko44
HumanMatt33
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); - var result = axe.testUtils + const node = fixture.querySelector('table'); + const result = axe.testUtils .getCheckEvaluate('td-has-header') .call(checkContext, node); @@ -47,107 +48,162 @@ describe('td-has-header', function () { assert.equal(checkContext._relatedNodes.length, 4); }); - it('should return true each non-empty cell has a row header', function () { - fixture.innerHTML = - '' + ' ' + '
hi hello
'; + it('should return true each non-empty cell has a row header', () => { + fixture.innerHTML = html` + + + + + +
hihello
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils.getCheckEvaluate('td-has-header').call(checkContext, node) ); }); - it('should return true each non-empty cell has a column header', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
hi hello
hi hello
'; + it('should return true each non-empty cell has a column header', () => { + fixture.innerHTML = html` + + + + + + + + + +
hihello
hihello
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils.getCheckEvaluate('td-has-header').call(checkContext, node) ); }); - it('should return true each non-empty cell has aria-label', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
hi hello
hi hello
'; + it('should return true each non-empty cell has aria-label', () => { + fixture.innerHTML = html` + + + + + + + + + +
hihello
hihello
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils.getCheckEvaluate('td-has-header').call(checkContext, node) ); }); - it('should return true each non-empty cell has aria-labelledby', function () { - fixture.innerHTML = - '
one
two
' + - '' + - ' ' + - ' ' + - '
hi hello
hi hello
'; + it('should return true each non-empty cell has aria-labelledby', () => { + fixture.innerHTML = html` +
one
+
two
+ + + + + + + + + +
hihello
hihello
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils.getCheckEvaluate('td-has-header').call(checkContext, node) ); }); - it('should return true each non-empty cell has a headers attribute', function () { + it('should return true each non-empty cell has a headers attribute', () => { // This will fail under td-headers-attr because the headers must be inside the table - fixture.innerHTML = - '
one
two
' + - '' + - ' ' + - ' ' + - '
hi hello
hi hello
'; + fixture.innerHTML = html` +
one
+
two
+ + + + + + + + + +
hihello
hihello
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils.getCheckEvaluate('td-has-header').call(checkContext, node) ); }); - it('should return true there is at least one non-empty header', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
hi hello
hello
'; + it('should return true there is at least one non-empty header', () => { + fixture.innerHTML = html` + + + + + + + + + +
hihello
hello
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils.getCheckEvaluate('td-has-header').call(checkContext, node) ); }); - it('should return true if the only data cells are empty', function () { - fixture.innerHTML = - '' + ' ' + '
'; + it('should return true if the only data cells are empty', () => { + fixture.innerHTML = html` + + + + + +
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils.getCheckEvaluate('td-has-header').call(checkContext, node) ); }); - it('should return false if a cell has no headers', function () { - fixture.innerHTML = - '' + ' ' + '
hi hello
'; + it('should return false if a cell has no headers', () => { + fixture.innerHTML = html` + + + + + +
hihello
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isFalse( axe.testUtils.getCheckEvaluate('td-has-header').call(checkContext, node) @@ -158,15 +214,22 @@ describe('td-has-header', function () { ]); }); - it('should return false if a cell has no headers - complex table', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
Psuedo-Caption
hi hello Ok
'; + it('should return false if a cell has no headers - complex table', () => { + fixture.innerHTML = html` + + + + + + + + + +
Psuedo-Caption
hihelloOk
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isFalse( axe.testUtils.getCheckEvaluate('td-has-header').call(checkContext, node) @@ -179,61 +242,81 @@ describe('td-has-header', function () { ]); }); - it('should return true if the headers element is empty', function () { - fixture.innerHTML = - '' + - ' ' + - '
Hello goodbye
'; + it('should return true if the headers element is empty', () => { + fixture.innerHTML = html` + + + + + +
Hellogoodbye
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils.getCheckEvaluate('td-has-header').call(checkContext, node) ); }); - it('should return true if the headers element refers to non-existing elements', function () { - fixture.innerHTML = - '' + - ' ' + - '
Hello goodbye
'; + it('should return true if the headers element refers to non-existing elements', () => { + fixture.innerHTML = html` + + + + + +
Hellogoodbye
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils.getCheckEvaluate('td-has-header').call(checkContext, node) ); }); - it('should return false if all headers are empty', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
hi hello
'; + it('should return false if all headers are empty', () => { + fixture.innerHTML = html` + + + + + + + + + +
hihello
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isFalse( axe.testUtils.getCheckEvaluate('td-has-header').call(checkContext, node) ); }); - (shadowSupport ? it : xit)('recognizes shadow tree content', function () { + (shadowSupport ? it : xit)('recognizes shadow tree content', () => { fixture.innerHTML = '
header
'; - var shadow = fixture + const shadow = fixture .querySelector('#shadow') .attachShadow({ mode: 'open' }); - shadow.innerHTML = - '' + - ' ' + - ' ' + - '
data
'; + shadow.innerHTML = html` + + + + + + + +
data
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = axe.utils.querySelectorAll(axe._tree, 'table')[0].actualNode; + const node = axe.utils.querySelectorAll(axe._tree, 'table')[0].actualNode; assert.isTrue( axe.testUtils.getCheckEvaluate('td-has-header').call(checkContext, node) ); diff --git a/test/checks/tables/td-headers-attr.js b/test/checks/tables/td-headers-attr.js index 4fe08bc0b..99e1683ce 100644 --- a/test/checks/tables/td-headers-attr.js +++ b/test/checks/tables/td-headers-attr.js @@ -1,136 +1,262 @@ -describe('td-headers-attr', function () { - 'use strict'; +describe('td-headers-attr', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var checkContext = axe.testUtils.MockCheckContext(); - var fixtureSetup = axe.testUtils.fixtureSetup; - var check = axe.testUtils.getCheckEvaluate('td-headers-attr'); + const fixture = document.getElementById('fixture'); + const checkContext = axe.testUtils.MockCheckContext(); + const fixtureSetup = axe.testUtils.fixtureSetup; + const check = axe.testUtils.getCheckEvaluate('td-headers-attr'); - afterEach(function () { + afterEach(() => { checkContext.reset(); }); - it('returns true no headers attribute is present', function () { - fixtureSetup( - '' + - ' ' + - ' ' + - '
hi hello
hi hello
' - ); + it('returns true no headers attribute is present', () => { + fixtureSetup(html` + + + + + + + + + +
hihello
hihello
+ `); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue(check.call(checkContext, node)); }); - it('returns true if a valid header is present', function () { - fixtureSetup( - '' + - ' ' + - ' ' + - '
hello
goodbye
' - ); + it('returns true if a valid header is present', () => { + fixtureSetup(html` + + + + + + + +
hello
goodbye
+ `); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue(check.call(checkContext, node)); }); - it('returns true if multiple valid headers are present', function () { - fixtureSetup( - '' + - ' ' + - ' ' + - '
hello hello
goodbye
' - ); + it('returns true if multiple valid headers are present', () => { + fixtureSetup(html` + + + + + + + + +
hellohello
+ goodbye +
+ `); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue(check.call(checkContext, node)); }); - it('returns true with an empty header', function () { - fixtureSetup( - '' + - ' ' + - ' ' + - '
goodbye
' - ); + it('returns true with an empty header', () => { + fixtureSetup(html` + + + + + + + +
goodbye
+ `); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue(check.call(checkContext, node)); }); - it('returns undefined if headers is empty', function () { - fixtureSetup( - '' + - ' ' + - ' ' + - '
goodbye
' - ); + it('returns undefined if headers is empty', () => { + fixtureSetup(html` + + + + + + + +
goodbye
+ `); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isUndefined(check.call(checkContext, node)); }); - it('returns false if the header is a table cell', function () { - var node; + it('returns false if the header is a table cell', () => { + let node; - fixtureSetup( - '' + - ' ' + - ' ' + - '
hello
goodbye
' - ); + fixtureSetup(html` + + + + + + + +
hello
goodbye
+ `); node = fixture.querySelector('table'); assert.isFalse(check.call(checkContext, node)); - fixtureSetup( - 'hello' + - '' + - ' ' + - ' ' + - '
goodbye
' - ); + fixtureSetup(html` + hello + + + + + + + +
goodbye
+ `); node = fixture.querySelector('table'); assert.isFalse(check.call(checkContext, node)); + assert.deepEqual(checkContext._data, { + messageKey: 'cell-header-not-in-table' + }); - fixtureSetup( - '' + - ' ' + - ' ' + - '
hello
goodbye
' - ); + fixtureSetup(html` + + + + + + + +
hello
goodbye
+ `); node = fixture.querySelector('table'); assert.isFalse(check.call(checkContext, node)); }); - it('returns false if the header refers to the same cell', function () { - fixtureSetup( - '' + - ' ' + - ' ' + - '
hello
goodbye
' - ); + it('returns false if table cell referenced as header', () => { + fixtureSetup(html` + + + + + + + +
hello
goodbye
+ `); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isFalse(check.call(checkContext, node)); + assert.deepEqual(checkContext._data, { messageKey: 'cell-header-not-th' }); }); - it('returns true if td[headers] is hidden', function () { - fixtureSetup( - '' + - ' ' + - '
Hello
' - ); - var node = fixture.querySelector('table'); + it('returns true if table cell referenced as header with role rowheader or columnheader', () => { + let node; + + fixtureSetup(html` + + + + + + + +
hello
goodbye
+ `); + + node = fixture.querySelector('table'); + assert.isTrue(check.call(checkContext, node)); + + fixtureSetup(html` + + + + + + + +
hello
goodbye
+ `); + + node = fixture.querySelector('table'); + assert.isTrue(check.call(checkContext, node)); + }); + + it('relatedNodes contains each cell only once', () => { + fixtureSetup(html` + + + + + + + + + + +
hello
hello
goodbye
+ ' + `); + + const node = fixture.querySelector('table'); + check.call(checkContext, node); + assert.deepEqual(checkContext._relatedNodes, [ + fixture.querySelector('#bye') + ]); + }); + + it('returns false if the header refers to the same cell', () => { + fixtureSetup(html` + + + + + + + +
hello
goodbye
+ `); + + const node = fixture.querySelector('table'); + assert.isFalse(check.call(checkContext, node)); + assert.deepEqual(checkContext._data, { messageKey: 'header-refs-self' }); + }); + + it('returns true if td[headers] is hidden', () => { + fixtureSetup(html` + + + + + +
Hello
+ `); + const node = fixture.querySelector('table'); assert.isTrue(check.call(checkContext, node)); }); - it('returns true if td[headers] has aria-hidden=true', function () { - fixtureSetup( - '' + - ' ' + - '
Hello
' - ); - var node = fixture.querySelector('table'); + it('returns true if td[headers] has aria-hidden=true', () => { + fixtureSetup(html` + + + + + +
Hello
+ `); + const node = fixture.querySelector('table'); assert.isTrue(check.call(checkContext, node)); }); }); diff --git a/test/checks/tables/th-has-data-cells.js b/test/checks/tables/th-has-data-cells.js index 37058aea6..3d0c9b67f 100644 --- a/test/checks/tables/th-has-data-cells.js +++ b/test/checks/tables/th-has-data-cells.js @@ -1,24 +1,29 @@ -describe('th-has-data-cells', function () { - 'use strict'; +describe('th-has-data-cells', () => { + const html = axe.testUtils.html; + const fixture = document.getElementById('fixture'); + const shadowSupport = axe.testUtils.shadowSupport.v1; + const checkContext = axe.testUtils.MockCheckContext(); - var fixture = document.getElementById('fixture'); - var shadowSupport = axe.testUtils.shadowSupport.v1; - var checkContext = axe.testUtils.MockCheckContext(); - - afterEach(function () { - fixture.innerHTML = ''; + afterEach(() => { checkContext.reset(); }); - it('should return true each row header has a non-empty cell', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
hi hello
hi hello
'; + it('should return true each row header has a non-empty cell', () => { + fixture.innerHTML = html` + + + + + + + + + +
hihello
hihello
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils .getCheckEvaluate('th-has-data-cells') @@ -26,15 +31,22 @@ describe('th-has-data-cells', function () { ); }); - it('should return true each non-empty column header has a cell', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
H H
hi hello
'; + it('should return true each non-empty column header has a cell', () => { + fixture.innerHTML = html` + + + + + + + + + +
HH
hihello
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils .getCheckEvaluate('th-has-data-cells') @@ -42,15 +54,22 @@ describe('th-has-data-cells', function () { ); }); - it('should return true if referred to with headers attr', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
hi hello
H H
'; + it('should return true if referred to with headers attr', () => { + fixture.innerHTML = html` + + + + + + + + + +
hihello
HH
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils .getCheckEvaluate('th-has-data-cells') @@ -58,15 +77,22 @@ describe('th-has-data-cells', function () { ); }); - it('should return true if referred to with aria-labelledby', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
hi hello
H H
'; + it('should return true if referred to with aria-labelledby', () => { + fixture.innerHTML = html` + + + + + + + + + +
hihello
HH
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils .getCheckEvaluate('th-has-data-cells') @@ -74,14 +100,21 @@ describe('th-has-data-cells', function () { ); }); - it('should return true if the th element is empty', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
'; + it('should return true if the th element is empty', () => { + fixture.innerHTML = html` + + + + + + + + + +
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils .getCheckEvaluate('th-has-data-cells') @@ -89,20 +122,46 @@ describe('th-has-data-cells', function () { ); }); - it('should return true when the td has a content element', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - '
hi
hi
hi
hi foo
hi
hi
hi
'; + it('should return true when the td has a content element', () => { + fixture.innerHTML = html` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
hi
hi
hi + +
hifoo
hi
hi
hi
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils .getCheckEvaluate('th-has-data-cells') @@ -110,15 +169,20 @@ describe('th-has-data-cells', function () { ); }); - it('should return undefined if a th has no data cells', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
hi
hi
'; + it('should return undefined if a th has no data cells', () => { + fixture.innerHTML = html` + + + + + + + +
hi
hi
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isUndefined( axe.testUtils .getCheckEvaluate('th-has-data-cells') @@ -126,15 +190,22 @@ describe('th-has-data-cells', function () { ); }); - it('should return true if all data cells are empty', function () { - fixture.innerHTML = - '' + - ' ' + - ' ' + - '
hi
hi
'; + it('should return true if all data cells are empty', () => { + fixture.innerHTML = html` + + + + + + + + + +
hi
hi
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isTrue( axe.testUtils .getCheckEvaluate('th-has-data-cells') @@ -142,14 +213,15 @@ describe('th-has-data-cells', function () { ); }); - it('should return undefined if a td with role=columnheader is used that has no data cells', function () { - fixture.innerHTML = - '' + - ' ' + - '
axe AXE
'; + it('should return undefined if a td with role=columnheader is used that has no data cells', () => { + fixture.innerHTML = html` + + +
axe AXE
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isUndefined( axe.testUtils .getCheckEvaluate('th-has-data-cells') @@ -157,21 +229,22 @@ describe('th-has-data-cells', function () { ); }); - it('should return undefined if table cell points to a different header', function () { - fixture.innerHTML = - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
Column 1Column 2
'; + it('should return undefined if table cell points to a different header', () => { + fixture.innerHTML = html` + + + + + + + + + +
Column 1Column 2
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = fixture.querySelector('table'); + const node = fixture.querySelector('table'); assert.isUndefined( axe.testUtils .getCheckEvaluate('th-has-data-cells') @@ -179,19 +252,24 @@ describe('th-has-data-cells', function () { ); }); - (shadowSupport ? it : xit)('recognizes shadow tree content', function () { + (shadowSupport ? it : xit)('recognizes shadow tree content', () => { fixture.innerHTML = '
data
'; - var shadow = fixture + const shadow = fixture .querySelector('#shadow') .attachShadow({ mode: 'open' }); - shadow.innerHTML = - '' + - ' ' + - ' ' + - '
header
'; + shadow.innerHTML = html` + + + + + + + +
header
+ `; axe.testUtils.flatTreeSetup(fixture); - var node = axe.utils.querySelectorAll(axe._tree, 'table')[0].actualNode; + const node = axe.utils.querySelectorAll(axe._tree, 'table')[0].actualNode; assert.isTrue( axe.testUtils .getCheckEvaluate('th-has-data-cells') diff --git a/test/checks/visibility/hidden-content.js b/test/checks/visibility/hidden-content.js index 41211b94f..5ef6a7896 100644 --- a/test/checks/visibility/hidden-content.js +++ b/test/checks/visibility/hidden-content.js @@ -1,20 +1,20 @@ /* global xit */ -describe('hidden content', function () { - 'use strict'; +describe('hidden content', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var shadowSupport = axe.testUtils.shadowSupport.v1; - var checkSetup = axe.testUtils.checkSetup; - var checkContext = axe.testUtils.MockCheckContext(); + const fixture = document.getElementById('fixture'); + const shadowSupport = axe.testUtils.shadowSupport.v1; + const checkSetup = axe.testUtils.checkSetup; + const checkContext = axe.testUtils.MockCheckContext(); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; checkContext.reset(); axe._tree = undefined; }); - it('should return undefined with display:none and children', function () { - var params = checkSetup( + it('should return undefined with display:none and children', () => { + const params = checkSetup( '' ); assert.isUndefined( @@ -24,8 +24,8 @@ describe('hidden content', function () { ); }); - it('should return undefined with visibility:hidden and children', function () { - var params = checkSetup( + it('should return undefined with visibility:hidden and children', () => { + const params = checkSetup( '' ); assert.isUndefined( @@ -35,8 +35,8 @@ describe('hidden content', function () { ); }); - it('should return true with visibility:hidden and parent with visibility:hidden', function () { - var params = checkSetup( + it('should return true with visibility:hidden and parent with visibility:hidden', () => { + const params = checkSetup( '
' ); assert.isTrue( @@ -46,8 +46,8 @@ describe('hidden content', function () { ); }); - it('should return true with aria-hidden and no content', function () { - var params = checkSetup( + it('should return true with aria-hidden and no content', () => { + const params = checkSetup( '' ); assert.isTrue( @@ -57,10 +57,10 @@ describe('hidden content', function () { ); }); - it('should skip whitelisted elements', function () { - var node = document.querySelector('head'); + it('should skip whitelisted elements', () => { + const node = document.querySelector('head'); axe.testUtils.flatTreeSetup(document.documentElement); - var virtualNode = axe.utils.getNodeFromTree(node); + const virtualNode = axe.utils.getNodeFromTree(node); assert.isTrue( axe.testUtils.getCheckEvaluate('hidden-content')( node, @@ -70,17 +70,20 @@ describe('hidden content', function () { ); }); - (shadowSupport ? it : xit)('works on elements in a shadow DOM', function () { + (shadowSupport ? it : xit)('works on elements in a shadow DOM', () => { fixture.innerHTML = '
text
'; - var shadowRoot = document + const shadowRoot = document .getElementById('shadow') .attachShadow({ mode: 'open' }); - shadowRoot.innerHTML = - ''; + shadowRoot.innerHTML = html` + + `; axe.testUtils.flatTreeSetup(fixture); - var shadow = document.querySelector('#shadow'); - var virtualShadow = axe.utils.getNodeFromTree(shadow); + const shadow = document.querySelector('#shadow'); + const virtualShadow = axe.utils.getNodeFromTree(shadow); assert.isTrue( axe.testUtils.getCheckEvaluate('hidden-content')( shadow, @@ -89,8 +92,8 @@ describe('hidden content', function () { ) ); - var target = shadowRoot.querySelector('#target'); - var virtualTarget = axe.utils.getNodeFromTree(target); + const target = shadowRoot.querySelector('#target'); + const virtualTarget = axe.utils.getNodeFromTree(target); assert.isUndefined( axe.testUtils.getCheckEvaluate('hidden-content')( target, @@ -99,8 +102,8 @@ describe('hidden content', function () { ) ); - var content = document.querySelector('#content'); - var virtualContent = axe.utils.getNodeFromTree(content); + const content = document.querySelector('#content'); + const virtualContent = axe.utils.getNodeFromTree(content); assert.isTrue( axe.testUtils.getCheckEvaluate('hidden-content')( content, diff --git a/test/commons/aria/allowed-attr.js b/test/commons/aria/allowed-attr.js index 575b3e427..a6d2871e4 100644 --- a/test/commons/aria/allowed-attr.js +++ b/test/commons/aria/allowed-attr.js @@ -1,17 +1,15 @@ -describe('aria.allowedAttr', function () { - 'use strict'; - - var globalAttrs; - before(function () { +describe('aria.allowedAttr', () => { + let globalAttrs; + before(() => { axe._load({}); globalAttrs = axe.commons.standards.getGlobalAriaAttrs(); }); - afterEach(function () { + afterEach(() => { axe.reset(); }); - it('should returned the attributes property for the proper role', function () { + it('should returned the attributes property for the proper role', () => { axe.configure({ standards: { ariaRoles: { @@ -28,7 +26,7 @@ describe('aria.allowedAttr', function () { ); }); - it('should also check required attributes', function () { + it('should also check required attributes', () => { axe.configure({ standards: { ariaRoles: { @@ -46,7 +44,7 @@ describe('aria.allowedAttr', function () { ); }); - it('should return an array with globally allowed attributes', function () { + it('should return an array with globally allowed attributes', () => { assert.deepEqual(axe.commons.aria.allowedAttr('cats'), globalAttrs); }); }); diff --git a/test/commons/aria/arialabel-text.js b/test/commons/aria/arialabel-text.js index d29a8bc55..b204d28b6 100644 --- a/test/commons/aria/arialabel-text.js +++ b/test/commons/aria/arialabel-text.js @@ -1,37 +1,36 @@ -describe('aria.arialabelText', function () { - 'use strict'; - var aria = axe.commons.aria; +describe('aria.arialabelText', () => { + const aria = axe.commons.aria; - it('returns "" if there is no aria-label', function () { - var vNode = new axe.SerialVirtualNode({ nodeName: 'div' }); + it('returns "" if there is no aria-label', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'div' }); assert.equal(aria.arialabelText(vNode), ''); }); - it('returns the aria-label attribute', function () { - var label = ' my label '; - var vNode = new axe.SerialVirtualNode({ + it('returns the aria-label attribute', () => { + const label = ' my label '; + const vNode = new axe.SerialVirtualNode({ nodeName: 'div', attributes: { 'aria-label': label } }); assert.equal(aria.arialabelText(vNode), label); }); - it('returns "" if there is no aria-label', function () { - var vNode = new axe.SerialVirtualNode({ nodeName: 'div' }); + it('returns "" if there is no aria-label', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'div' }); assert.equal(aria.arialabelText(vNode), ''); }); - it('looks up the node in the flat tree', function () { - var label = 'harambe'; - var node = document.createElement('div'); + it('looks up the node in the flat tree', () => { + const label = 'harambe'; + const node = document.createElement('div'); node.setAttribute('aria-label', label); axe.utils.getFlattenedTree(node); assert.equal(aria.arialabelText(node), label); }); - it('returns "" if the node is not an element', function () { - var node = document.createTextNode('my text node'); + it('returns "" if the node is not an element', () => { + const node = document.createTextNode('my text node'); assert.equal(aria.arialabelText(node), ''); }); }); diff --git a/test/commons/aria/arialabelledby-text.js b/test/commons/aria/arialabelledby-text.js index 49161d702..5a402b36e 100644 --- a/test/commons/aria/arialabelledby-text.js +++ b/test/commons/aria/arialabelledby-text.js @@ -1,110 +1,111 @@ -describe('aria.arialabelledbyText', function () { - 'use strict'; - var aria = axe.commons.aria; - var queryFixture = axe.testUtils.queryFixture; +describe('aria.arialabelledbyText', () => { + const html = axe.testUtils.html; + const aria = axe.commons.aria; + const queryFixture = axe.testUtils.queryFixture; - it('returns the accessible name of the aria-labelledby references', function () { - var target = queryFixture( - '
' + - '
Foo text
' - ); - var accName = aria.arialabelledbyText(target); + it('returns the accessible name of the aria-labelledby references', () => { + const target = queryFixture(html` +
+
Foo text
+ `); + const accName = aria.arialabelledbyText(target); assert.equal(accName, 'Foo text'); }); - it('works with virtual nodes', function () { - var target = queryFixture( - '
' + - '
Foo text
' - ); - var accName = aria.arialabelledbyText(target); + it('works with virtual nodes', () => { + const target = queryFixture(html` +
+
Foo text
+ `); + const accName = aria.arialabelledbyText(target); assert.equal(accName, 'Foo text'); }); - it('returns references in order', function () { - var target = queryFixture( - '
' + - '
Foo
' + - '
Bar
' + - '
Baz
' - ); - var accName = aria.arialabelledbyText(target); + it('returns references in order', () => { + const target = queryFixture(html` +
+
Foo
+
Bar
+
Baz
+ `); + const accName = aria.arialabelledbyText(target); assert.equal(accName, 'Bar Baz Foo'); }); - it('returns "" if the node is not an element', function () { - var target = queryFixture('
foo
'); - var accName = aria.arialabelledbyText(target.actualNode.firstChild); + it('returns "" if the node is not an element', () => { + const target = queryFixture('
foo
'); + const accName = aria.arialabelledbyText(target.actualNode.firstChild); assert.equal(accName, ''); }); - it('returns "" with context.inLabelledByContext: true', function () { - var target = queryFixture( - '
' + - '
Foo text
' - ); - var accName = aria.arialabelledbyText(target, { + it('returns "" with context.inLabelledByContext: true', () => { + const target = queryFixture(html` +
+
Foo text
+ `); + const accName = aria.arialabelledbyText(target, { inLabelledByContext: true }); assert.equal(accName, ''); }); - it('returns "" with context.inControlContext: true', function () { - var target = queryFixture( - '
' + - '
Foo text
' - ); - var accName = aria.arialabelledbyText(target, { + it('returns "" with context.inControlContext: true', () => { + const target = queryFixture(html` +
+
Foo text
+ `); + const accName = aria.arialabelledbyText(target, { inControlContext: true }); assert.equal(accName, ''); }); - it('returns content of a aria-hidden reference', function () { - var target = queryFixture( - '
' + - '' - ); - var accName = aria.arialabelledbyText(target); + it('returns content of a aria-hidden reference', () => { + const target = queryFixture(html` +
+ + `); + const accName = aria.arialabelledbyText(target); assert.equal(accName, 'Foo text'); }); - it('returns content of a `display:none` reference', function () { - var target = queryFixture( - '
' + - '' - ); - var accName = aria.arialabelledbyText(target); + it('returns content of a `display:none` reference', () => { + const target = queryFixture(html` +
+ + `); + const accName = aria.arialabelledbyText(target); assert.equal(accName, 'Foo text'); }); - it('returns does not return hidden content of a visible reference', function () { - var target = queryFixture( - '
' + - '
Foo text
' - ); - var accName = aria.arialabelledbyText(target); + it('returns does not return hidden content of a visible reference', () => { + const target = queryFixture(html` +
+
Foo text
+ `); + const accName = aria.arialabelledbyText(target); assert.equal(accName, ''); }); - it('does not follow more than one aria-labelledy reference', function () { - var target = queryFixture( - '
' + - '
' + - '
Foo text
' - ); - var accName = aria.arialabelledbyText(target, { + it('does not follow more than one aria-labelledy reference', () => { + const target = queryFixture(html` +
+
+
Foo text
+ `); + const accName = aria.arialabelledbyText(target, { inControlContext: true }); assert.equal(accName, ''); }); - it('preserves spacing', function () { - var target = queryFixture( - '
' + - '
\t Foo \n text \t
' + it('preserves spacing', () => { + const spaced = ' \t Foo \n text \t '; + const target = queryFixture( + html`
+
${spaced}
` ); - var accName = aria.arialabelledbyText(target); - assert.equal(accName, ' \t Foo \n text \t '); + const accName = aria.arialabelledbyText(target); + assert.equal(accName, spaced); }); }); diff --git a/test/commons/aria/get-accessible-refs.js b/test/commons/aria/get-accessible-refs.js index 00820526e..b5789acd0 100644 --- a/test/commons/aria/get-accessible-refs.js +++ b/test/commons/aria/get-accessible-refs.js @@ -1,9 +1,9 @@ -describe('aria.getAccessibleRefs', function () { - 'use strict'; +describe('aria.getAccessibleRefs', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var getAccessibleRefs = axe.commons.aria.getAccessibleRefs; - var shadowSupport = axe.testUtils.shadowSupport.v1; + const fixture = document.getElementById('fixture'); + const getAccessibleRefs = axe.commons.aria.getAccessibleRefs; + const shadowSupport = axe.testUtils.shadowSupport.v1; function setLookup(attrs) { axe.configure({ @@ -13,66 +13,66 @@ describe('aria.getAccessibleRefs', function () { }); } - before(function () { + before(() => { axe._load({}); }); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; axe.reset(); }); - it('returns empty array by default', function () { + it('returns empty array by default', () => { fixture.innerHTML = '
'; - var node = document.getElementById('foo'); + const node = document.getElementById('foo'); assert.lengthOf(getAccessibleRefs(node), 0); }); - it('returns array of nodes for IDs used in aria IDREF attributes', function () { + it('returns array of nodes for IDs used in aria IDREF attributes', () => { setLookup({ 'aria-foo': { type: 'idref' } }); fixture.innerHTML = '
'; - var node = document.getElementById('foo'); - var ref = document.getElementById('ref'); + const node = document.getElementById('foo'); + const ref = document.getElementById('ref'); assert.deepEqual(getAccessibleRefs(node), [ref]); }); - it('returns array of nodes for IDs used in aria IDREFS attributes', function () { + it('returns array of nodes for IDs used in aria IDREFS attributes', () => { setLookup({ 'aria-bar': { type: 'idrefs' } }); fixture.innerHTML = '
'; - var node1 = document.getElementById('foo'); - var node2 = document.getElementById('bar'); - var ref = document.getElementById('ref'); + const node1 = document.getElementById('foo'); + const node2 = document.getElementById('bar'); + const ref = document.getElementById('ref'); assert.deepEqual(getAccessibleRefs(node1), [ref]); assert.deepEqual(getAccessibleRefs(node2), [ref]); }); - it('returns array of nodes for IDs used in label[for] attributes', function () { + it('returns array of nodes for IDs used in label[for] attributes', () => { setLookup({ 'aria-foo': { type: 'idref' } }); fixture.innerHTML = ''; - var node = document.getElementById('baz'); - var ref = document.getElementById('ref'); + const node = document.getElementById('baz'); + const ref = document.getElementById('ref'); assert.deepEqual(getAccessibleRefs(node), [ref]); }); - it('returns all nodes used in aria IDREF attributes', function () { + it('returns all nodes used in aria IDREF attributes', () => { setLookup({ 'aria-bar': { type: 'idrefs' } }); fixture.innerHTML = '
'; - var node = document.getElementById('foo'); - var ref1 = document.getElementById('ref1'); - var ref2 = document.getElementById('ref2'); + const node = document.getElementById('foo'); + const ref1 = document.getElementById('ref1'); + const ref2 = document.getElementById('ref2'); assert.deepEqual(getAccessibleRefs(node), [ref1, ref2]); }); - it('does not break on a custom .children property', function () { + it('does not break on a custom .children property', () => { setLookup({ 'aria-foo': { type: 'idref' } }); fixture.innerHTML = '
'; - var node = document.getElementById('foo'); - var ref = document.getElementById('ref'); + const node = document.getElementById('foo'); + const ref = document.getElementById('ref'); Object.defineProperty(node, 'children', { value: ['#ref'] @@ -80,7 +80,7 @@ describe('aria.getAccessibleRefs', function () { assert.deepEqual(getAccessibleRefs(node), [ref]); }); - describe('when JavaScript object names are used as IDs', function () { + describe('when JavaScript object names are used as IDs', () => { const ids = [ 'prototype', 'constructor', @@ -91,14 +91,16 @@ describe('aria.getAccessibleRefs', function () { 'toString' ]; for (const id of ids) { - it(`does not break with id="${id}"`, function () { + it(`does not break with id="${id}"`, () => { setLookup({ 'aria-bar': { type: 'idrefs' } }); - fixture.innerHTML = `
`; - - var node = document.getElementById(id); - var ref = document.getElementById('ref'); + fixture.innerHTML = html`
+ `; + + const node = document.getElementById(id); + const ref = document.getElementById('ref'); assert.deepEqual( getAccessibleRefs(node), [ref], @@ -108,27 +110,29 @@ describe('aria.getAccessibleRefs', function () { } }); - (shadowSupport ? it : xit)('works inside shadow DOM', function () { + (shadowSupport ? it : xit)('works inside shadow DOM', () => { setLookup({ 'aria-bar': { type: 'idref' } }); fixture.innerHTML = '
'; - var shadow = document.getElementById('foo').attachShadow({ mode: 'open' }); + const shadow = document + .getElementById('foo') + .attachShadow({ mode: 'open' }); shadow.innerHTML = '
'; - var node = shadow.getElementById('bar'); - var ref = shadow.getElementById('ref'); + const node = shadow.getElementById('bar'); + const ref = shadow.getElementById('ref'); assert.deepEqual(getAccessibleRefs(node), [ref]); }); (shadowSupport ? it : xit)( 'returns empty array for IDREFs inside shadow DOM', - function () { + () => { setLookup({ 'aria-foo': { type: 'idrefs' } }); fixture.innerHTML = '
'; - var node1 = document.getElementById('foo'); - var node2 = document.getElementById('bar'); + const node1 = document.getElementById('foo'); + const node2 = document.getElementById('bar'); - var shadow = node1.attachShadow({ mode: 'open' }); + const shadow = node1.attachShadow({ mode: 'open' }); shadow.innerHTML = '
'; assert.lengthOf(getAccessibleRefs(node1), 0); @@ -138,35 +142,35 @@ describe('aria.getAccessibleRefs', function () { (shadowSupport ? it : xit)( 'returns empty array for IDREFs outside shadow DOM', - function () { + () => { setLookup({ 'aria-bar': { type: 'idref' } }); fixture.innerHTML = '
'; - var shadow = document + const shadow = document .getElementById('foo') .attachShadow({ mode: 'open' }); shadow.innerHTML = '
'; - var node = shadow.getElementById('bar'); + const node = shadow.getElementById('bar'); assert.lengthOf(getAccessibleRefs(node), 0); } ); - (shadowSupport ? it : xit)('separates IDREFs by roots', function () { + (shadowSupport ? it : xit)('separates IDREFs by roots', () => { setLookup({ 'aria-bar': { type: 'idref' } }); fixture.innerHTML = '
'; - var shadow = document + const shadow = document .getElementById('shadow') .attachShadow({ mode: 'open' }); shadow.innerHTML = '
'; - var outsideNode = document.getElementById('foo'); - var outsideRef = document.getElementById('outside'); - var insideNode = shadow.getElementById('foo'); - var insideRef = shadow.getElementById('inside'); + const outsideNode = document.getElementById('foo'); + const outsideRef = document.getElementById('outside'); + const insideNode = shadow.getElementById('foo'); + const insideRef = shadow.getElementById('inside'); assert.deepEqual(getAccessibleRefs(outsideNode), [outsideRef]); assert.deepEqual(getAccessibleRefs(insideNode), [insideRef]); }); diff --git a/test/commons/aria/get-element-unallowed-roles.js b/test/commons/aria/get-element-unallowed-roles.js index f234abb64..56da46ef9 100644 --- a/test/commons/aria/get-element-unallowed-roles.js +++ b/test/commons/aria/get-element-unallowed-roles.js @@ -1,151 +1,151 @@ -describe('aria.getElementUnallowedRoles', function () { - var flatTreeSetup = axe.testUtils.flatTreeSetup; - var getElementUnallowedRoles = axe.commons.aria.getElementUnallowedRoles; +describe('aria.getElementUnallowedRoles', () => { + const flatTreeSetup = axe.testUtils.flatTreeSetup; + const getElementUnallowedRoles = axe.commons.aria.getElementUnallowedRoles; - it('returns unallowed role=application when used on a input elm', function () { - var node = document.createElement('input'); - var role = 'application'; + it('returns unallowed role=application when used on a input elm', () => { + const node = document.createElement('input'); + const role = 'application'; node.setAttribute('type', ''); node.setAttribute('aria-pressed', ''); node.setAttribute('role', role); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node); + const actual = getElementUnallowedRoles(node); assert.isNotEmpty(actual); assert.include(actual, role); }); - it('returns empty on type=checkbox and aria-pressed attr on input elm', function () { - var node = document.createElement('input'); + it('returns empty on type=checkbox and aria-pressed attr on input elm', () => { + const node = document.createElement('input'); node.setAttribute('type', 'checkbox'); node.setAttribute('aria-pressed', ''); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node); + const actual = getElementUnallowedRoles(node); assert.isEmpty(actual); }); - it('returns unallowed role=menubar when used on a li elm', function () { - var node = document.createElement('li'); - var role = 'menubar'; + it('returns unallowed role=menubar when used on a li elm', () => { + const node = document.createElement('li'); + const role = 'menubar'; node.setAttribute('role', role); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node); + const actual = getElementUnallowedRoles(node); assert.isNotEmpty(actual, role); }); - it('returns empty on role=menuitemcheckbox with type=button on input elm', function () { - var node = document.createElement('input'); - var role = 'menuitemcheckbox'; + it('returns empty on role=menuitemcheckbox with type=button on input elm', () => { + const node = document.createElement('input'); + const role = 'menuitemcheckbox'; node.setAttribute('role', role); node.setAttribute('type', 'button'); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node); + const actual = getElementUnallowedRoles(node); assert.isEmpty(actual); }); - it('returns unallowed role=option when used on section elm', function () { - var node = document.createElement('section'); - var role = 'option'; + it('returns unallowed role=option when used on section elm', () => { + const node = document.createElement('section'); + const role = 'option'; node.setAttribute('role', role); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node); + const actual = getElementUnallowedRoles(node); assert.isNotEmpty(actual); assert.include(actual, role); }); - it('returns empty on role=menuitemradio and type=radio on input elm', function () { - var node = document.createElement('input'); - var role = 'menuitemradio'; + it('returns empty on role=menuitemradio and type=radio on input elm', () => { + const node = document.createElement('input'); + const role = 'menuitemradio'; node.setAttribute('role', role); node.setAttribute('type', 'radio'); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node); + const actual = getElementUnallowedRoles(node); assert.isEmpty(actual); }); - it('returns unallowed role=textbox on a input elm and allowImplicit is true (default)', function () { - var node = document.createElement('input'); - var role = 'textbox'; + it('returns unallowed role=textbox on a input elm and allowImplicit is true (default)', () => { + const node = document.createElement('input'); + const role = 'textbox'; node.setAttribute('role', role); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node, true); + const actual = getElementUnallowedRoles(node, true); assert.isEmpty(actual, role); }); - it('returns empty on role=button on div elm when role is not implicit and allowImplicit: false', function () { - var node = document.createElement('div'); + it('returns empty on role=button on div elm when role is not implicit and allowImplicit: false', () => { + const node = document.createElement('div'); node.setAttribute('role', 'button'); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node, false); + const actual = getElementUnallowedRoles(node, false); assert.isEmpty(actual); }); - it('returns unallowed role=contentinfo on footer elm and allowImplicit: false', function () { - var node = document.createElement('footer'); + it('returns unallowed role=contentinfo on footer elm and allowImplicit: false', () => { + const node = document.createElement('footer'); node.setAttribute('role', 'contentinfo'); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node, false); + const actual = getElementUnallowedRoles(node, false); assert.isNotEmpty(actual, 'contentinfo'); }); - it('returns unallowed role=banner on header elm and allowImplicit:false', function () { - var node = document.createElement('header'); + it('returns unallowed role=banner on header elm and allowImplicit:false', () => { + const node = document.createElement('header'); node.setAttribute('role', 'banner'); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node, false); + const actual = getElementUnallowedRoles(node, false); assert.isNotEmpty(actual, 'banner'); }); - it('returns empty on role=contentinfo on footer elm when allowImplicit:true', function () { - var node = document.createElement('footer'); + it('returns empty on role=contentinfo on footer elm when allowImplicit:true', () => { + const node = document.createElement('footer'); node.setAttribute('role', 'contentinfo'); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node); + const actual = getElementUnallowedRoles(node); assert.isEmpty(actual); }); - it('returns empty on role=banner on header elm when allowImplicit:true', function () { - var node = document.createElement('header'); + it('returns empty on role=banner on header elm when allowImplicit:true', () => { + const node = document.createElement('header'); node.setAttribute('role', 'banner'); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node); + const actual = getElementUnallowedRoles(node); assert.isEmpty(actual); }); - it('returns empty role=doc-backlink on anchor elm and allowImplicit:false', function () { - var node = document.createElement('a'); + it('returns empty role=doc-backlink on anchor elm and allowImplicit:false', () => { + const node = document.createElement('a'); node.setAttribute('href', '#'); node.setAttribute('role', 'doc-backlink'); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node, false); + const actual = getElementUnallowedRoles(node, false); assert.isEmpty(actual); }); - it('returns empty on role=doc-backlink on anchor elm when allowImplicit:true', function () { - var node = document.createElement('a'); + it('returns empty on role=doc-backlink on anchor elm when allowImplicit:true', () => { + const node = document.createElement('a'); node.setAttribute('href', '#'); node.setAttribute('role', 'doc-backlink'); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node); + const actual = getElementUnallowedRoles(node); assert.isEmpty(actual); }); - it('returns unallowed role=doc-backlink on anchor elm without href attr and allowImplicit:false', function () { - var node = document.createElement('a'); + it('returns unallowed role=doc-backlink on anchor elm without href attr and allowImplicit:false', () => { + const node = document.createElement('a'); node.setAttribute('role', 'doc-backlink'); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node, false); + const actual = getElementUnallowedRoles(node, false); assert.isNotEmpty(actual, 'doc-backlink'); }); - it('returns unallowed role=doc-backlink on anchor elm without href attr and allowImplicit:true', function () { - var node = document.createElement('a'); + it('returns unallowed role=doc-backlink on anchor elm without href attr and allowImplicit:true', () => { + const node = document.createElement('a'); node.setAttribute('role', 'doc-backlink'); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node); + const actual = getElementUnallowedRoles(node); assert.isNotEmpty(actual, 'doc-backlink'); }); - it('returns empty role=banner on header elm when using axe.configure and allowImplicit:false', function () { + it('returns empty role=banner on header elm when using axe.configure and allowImplicit:false', () => { axe.configure({ standards: { htmlElms: { @@ -156,14 +156,14 @@ describe('aria.getElementUnallowedRoles', function () { } } }); - var node = document.createElement('header'); + const node = document.createElement('header'); node.setAttribute('role', 'banner'); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node, false); + const actual = getElementUnallowedRoles(node, false); assert.isEmpty(actual); }); - it('returns empty role=contentinfo on footer elm when using axe.configure and allowImplicit:false', function () { + it('returns empty role=contentinfo on footer elm when using axe.configure and allowImplicit:false', () => { axe.configure({ standards: { htmlElms: { @@ -174,35 +174,35 @@ describe('aria.getElementUnallowedRoles', function () { } } }); - var node = document.createElement('footer'); + const node = document.createElement('footer'); node.setAttribute('role', 'contentinfo'); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node, false); + const actual = getElementUnallowedRoles(node, false); assert.isEmpty(actual); }); - it('returns unallowed role=row when when used on TR element and allowImplicit:false', function () { - var node = document.createElement('tr'); + it('returns unallowed role=row when when used on TR element and allowImplicit:false', () => { + const node = document.createElement('tr'); node.setAttribute('role', 'row'); flatTreeSetup(node); - var actual = getElementUnallowedRoles(node, false); + const actual = getElementUnallowedRoles(node, false); assert.isNotEmpty(actual, 'row'); }); - it('returns empty on type=checkbox and aria-pressed attr on SerialVirtualNode with a input elm', function () { - var vNode = new axe.SerialVirtualNode({ + it('returns empty on type=checkbox and aria-pressed attr on SerialVirtualNode with a input elm', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'input', attributes: { type: 'checkbox', 'aria-pressed': '' } }); - var actual = getElementUnallowedRoles(vNode); + const actual = getElementUnallowedRoles(vNode); assert.isEmpty(actual); }); - it('returns unallowed role=application for a SerialVirtualNode with a input elm', function () { - var vNode = new axe.SerialVirtualNode({ + it('returns unallowed role=application for a SerialVirtualNode with a input elm', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'input', attributes: { role: 'application', @@ -210,7 +210,7 @@ describe('aria.getElementUnallowedRoles', function () { 'aria-pressed': '' } }); - var actual = getElementUnallowedRoles(vNode); + const actual = getElementUnallowedRoles(vNode); assert.isNotEmpty(actual); assert.include(actual, 'application'); }); diff --git a/test/commons/aria/get-explicit-role.js b/test/commons/aria/get-explicit-role.js index b657248d2..3747218ef 100644 --- a/test/commons/aria/get-explicit-role.js +++ b/test/commons/aria/get-explicit-role.js @@ -1,141 +1,140 @@ -describe('aria.getExplicitRole', function () { - 'use strict'; - var aria = axe.commons.aria; - var roleDefinitions = aria.lookupTable.role; - var flatTreeSetup = axe.testUtils.flatTreeSetup; - - it('returns valid roles', function () { - var node = document.createElement('div'); +describe('aria.getExplicitRole', () => { + const aria = axe.commons.aria; + const roleDefinitions = aria.lookupTable.role; + const flatTreeSetup = axe.testUtils.flatTreeSetup; + + it('returns valid roles', () => { + const node = document.createElement('div'); node.setAttribute('role', 'button'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.equal(aria.getExplicitRole(vNode), 'button'); }); - it('handles case sensitivity', function () { - var node = document.createElement('div'); + it('handles case sensitivity', () => { + const node = document.createElement('div'); node.setAttribute('role', 'BUTTON'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.equal(aria.getExplicitRole(vNode), 'button'); }); - it('handles whitespacing', function () { - var node = document.createElement('div'); + it('handles whitespacing', () => { + const node = document.createElement('div'); node.setAttribute('role', ' button '); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.equal(aria.getExplicitRole(vNode), 'button'); }); - it('returns null when there is no role', function () { - var node = document.createElement('div'); - var vNode = flatTreeSetup(node)[0]; + it('returns null when there is no role', () => { + const node = document.createElement('div'); + const vNode = flatTreeSetup(node)[0]; assert.isNull(aria.getExplicitRole(vNode)); }); - it('returns the explicit role if it is valid and non-abstract', function () { - var node = document.createElement('li'); + it('returns the explicit role if it is valid and non-abstract', () => { + const node = document.createElement('li'); node.setAttribute('role', 'menuitem'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.equal(aria.getExplicitRole(vNode), 'menuitem'); }); - it('ignores fallback roles by default', function () { - var node = document.createElement('div'); + it('ignores fallback roles by default', () => { + const node = document.createElement('div'); node.setAttribute('role', 'spinbutton button'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.isNull(aria.getExplicitRole(vNode)); }); - it('returns null if the node is not an element', function () { - var node = document.createTextNode('foo bar baz'); - var vNode = flatTreeSetup(node)[0]; + it('returns null if the node is not an element', () => { + const node = document.createTextNode('foo bar baz'); + const vNode = flatTreeSetup(node)[0]; assert.isNull(aria.getExplicitRole(vNode)); }); - describe('abstracts', function () { - it('ignores abstract roles by default', function () { - var node = document.createElement('li'); + describe('abstracts', () => { + it('ignores abstract roles by default', () => { + const node = document.createElement('li'); node.setAttribute('role', 'section'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.equal(roleDefinitions.section.type, 'abstract'); assert.isNull(aria.getExplicitRole(vNode)); }); - it('returns abstract roles with `abstracts: true`', function () { - var node = document.createElement('li'); + it('returns abstract roles with `abstracts: true`', () => { + const node = document.createElement('li'); node.setAttribute('role', 'section'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.equal(roleDefinitions.section.type, 'abstract'); assert.equal(aria.getExplicitRole(vNode, { abstracts: true }), 'section'); }); - it('does not returns abstract roles with `abstracts: false`', function () { - var node = document.createElement('li'); + it('does not returns abstract roles with `abstracts: false`', () => { + const node = document.createElement('li'); node.setAttribute('role', 'section'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.equal(roleDefinitions.section.type, 'abstract'); assert.isNull(aria.getExplicitRole(vNode, { abstracts: false })); }); }); - describe('dpub', function () { - it('ignores DPUB roles by default', function () { - var node = document.createElement('section'); + describe('dpub', () => { + it('ignores DPUB roles by default', () => { + const node = document.createElement('section'); node.setAttribute('role', 'doc-chapter'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.isNull(aria.getExplicitRole(vNode)); }); - it('returns DPUB roles with `dpub: true`', function () { - var node = document.createElement('section'); + it('returns DPUB roles with `dpub: true`', () => { + const node = document.createElement('section'); node.setAttribute('role', 'doc-chapter'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.equal(aria.getExplicitRole(vNode, { dpub: true }), 'doc-chapter'); }); - it('does not returns DPUB roles with `dpub: false`', function () { - var node = document.createElement('section'); + it('does not returns DPUB roles with `dpub: false`', () => { + const node = document.createElement('section'); node.setAttribute('role', 'doc-chapter'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.isNull(aria.getExplicitRole(vNode, { dpub: false })); }); }); - describe('fallback', function () { - it('returns the first valid item in the list', function () { - var node = document.createElement('div'); + describe('fallback', () => { + it('returns the first valid item in the list', () => { + const node = document.createElement('div'); node.setAttribute('role', 'link button'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.equal(aria.getExplicitRole(vNode, { fallback: true }), 'link'); }); - it('skips over invalid roles', function () { - var node = document.createElement('div'); + it('skips over invalid roles', () => { + const node = document.createElement('div'); node.setAttribute('role', 'foobar button'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.equal(aria.getExplicitRole(vNode, { fallback: true }), 'button'); }); - it('returns the null if all roles are invalid and there is no implicit role', function () { - var node = document.createElement('div'); + it('returns the null if all roles are invalid and there is no implicit role', () => { + const node = document.createElement('div'); node.setAttribute('role', 'foo bar baz'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.isNull(aria.getExplicitRole(vNode, { fallback: true })); }); - it('respect the `abstracts` option', function () { - var node = document.createElement('li'); + it('respect the `abstracts` option', () => { + const node = document.createElement('li'); node.setAttribute('role', 'doc-chapter section'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.equal( aria.getExplicitRole(vNode, { fallback: true, abstracts: true }), 'section' ); }); - it('respect the `dpub` option', function () { - var node = document.createElement('li'); + it('respect the `dpub` option', () => { + const node = document.createElement('li'); node.setAttribute('role', 'doc-chapter section'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.equal( aria.getExplicitRole(vNode, { fallback: true, dpub: true }), 'doc-chapter' diff --git a/test/commons/aria/get-owned-virtual.js b/test/commons/aria/get-owned-virtual.js index 6011c16af..14e862974 100644 --- a/test/commons/aria/get-owned-virtual.js +++ b/test/commons/aria/get-owned-virtual.js @@ -1,35 +1,39 @@ -describe('aria.getOwnedVirtual', function () { - 'use strict'; - var aria = axe.commons.aria; - var fixtureSetup = axe.testUtils.fixtureSetup; +describe('aria.getOwnedVirtual', () => { + const html = axe.testUtils.html; + const aria = axe.commons.aria; + const fixtureSetup = axe.testUtils.fixtureSetup; - it('returns a list of children in order', function () { - fixtureSetup( - '
' + - '

heading 1

' + - '

heading 2

' + - '

heading 3

' + - '
' - ); - var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; - var owned = aria.getOwnedVirtual(target); + it('returns a list of children in order', () => { + fixtureSetup(html` +
+

heading 1

+

heading 2

+

heading 3

+
+ `); + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + const owned = aria + .getOwnedVirtual(target) + .filter(({ props }) => props.nodeType === 1); assert.lengthOf(owned, 3); assert.equal(owned[0].actualNode.nodeName.toUpperCase(), 'H1'); assert.equal(owned[1].actualNode.nodeName.toUpperCase(), 'H2'); assert.equal(owned[2].actualNode.nodeName.toUpperCase(), 'H3'); }); - it('adds aria-owned reffed elements to the children', function () { - fixtureSetup( - '
' + - '

heading 1

' + - '

heading 2

' + - '
' + - '

heading 4

' + - '

heading 3

' - ); - var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; - var owned = aria.getOwnedVirtual(target); + it('adds aria-owned reffed elements to the children', () => { + fixtureSetup(html` +
+

heading 1

+

heading 2

+
+

heading 4

+

heading 3

+ `); + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + const owned = aria + .getOwnedVirtual(target) + .filter(({ props }) => props.nodeType === 1); assert.lengthOf(owned, 4); assert.equal(owned[0].actualNode.nodeName.toUpperCase(), 'H1'); assert.equal(owned[1].actualNode.nodeName.toUpperCase(), 'H2'); @@ -37,63 +41,130 @@ describe('aria.getOwnedVirtual', function () { assert.equal(owned[3].actualNode.nodeName.toUpperCase(), 'H4'); }); - it('ignores whitespace-only aria-owned', function () { - fixtureSetup( - '
' + - '

heading 1

' + - '

heading 2

' + - '

heading 3

' + - '
' - ); - var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; - var owned = aria.getOwnedVirtual(target); + it('does not return duplicate when child is also aria-owned', () => { + fixtureSetup(html` +
+ +
+ `); + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + const owned = aria + .getOwnedVirtual(target) + .filter(({ props }) => props.nodeType === 1); + assert.lengthOf(owned, 1); + assert.equal(owned[0].actualNode.id, 'foo'); + }); + + it('does not return duplicate when same ID appears multiple times in aria-owns', () => { + fixtureSetup(html` +
+
A
+
B
+ `); + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + const owned = aria + .getOwnedVirtual(target) + .filter(({ props }) => props.nodeType === 1); + assert.lengthOf(owned, 2); + assert.equal(owned[0].actualNode.id, 'a'); + assert.equal(owned[1].actualNode.id, 'b'); + }); + + it('moves aria-owned child to the end', () => { + fixtureSetup(html` +
+
A
+
B
+
+ `); + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + const owned = aria + .getOwnedVirtual(target) + .filter(({ props }) => props.nodeType === 1); + assert.lengthOf(owned, 2); + assert.equal(owned[0].actualNode.id, 'b'); + assert.equal(owned[1].actualNode.id, 'a'); + }); + + it('moves multiple aria-owned children to the end in aria-owns order', () => { + fixtureSetup(html` +
+
A
+
B
+
C
+
+ `); + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + const owned = aria + .getOwnedVirtual(target) + .filter(({ props }) => props.nodeType === 1); + assert.lengthOf(owned, 3); + assert.equal(owned[0].actualNode.id, 'b'); + assert.equal(owned[1].actualNode.id, 'c'); + assert.equal(owned[2].actualNode.id, 'a'); + }); + + it('ignores whitespace-only aria-owned', () => { + fixtureSetup(html` +
+

heading 1

+

heading 2

+

heading 3

+
+ `); + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + const owned = aria + .getOwnedVirtual(target) + .filter(({ props }) => props.nodeType === 1); assert.lengthOf(owned, 3); assert.equal(owned[0].actualNode.nodeName.toUpperCase(), 'H1'); assert.equal(owned[1].actualNode.nodeName.toUpperCase(), 'H2'); assert.equal(owned[2].actualNode.nodeName.toUpperCase(), 'H3'); }); - it('ignores broken aria-owned refs', function () { - fixtureSetup( - '
' + - '

heading 1

' + - '

heading 2

' + - '

heading 3

' + - '
' - ); - var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; - var owned = aria.getOwnedVirtual(target); + it('ignores broken aria-owned refs', () => { + fixtureSetup(html` +
+

heading 1

+

heading 2

+

heading 3

+
+ `); + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + const owned = aria + .getOwnedVirtual(target) + .filter(({ props }) => props.nodeType === 1); assert.lengthOf(owned, 3); assert.equal(owned[0].actualNode.nodeName.toUpperCase(), 'H1'); assert.equal(owned[1].actualNode.nodeName.toUpperCase(), 'H2'); assert.equal(owned[2].actualNode.nodeName.toUpperCase(), 'H3'); }); - it('includes text nodes', function () { - fixtureSetup( - '
' + - 'text 1' + - '

heading 1

' + - 'text 2' + - '

heading 2

' + - ' \t\n' + - '
' - ); - var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; - var owned = aria.getOwnedVirtual(target); + it('includes text nodes', () => { + fixtureSetup(html` +
+ text 1 +

heading 1

+ text 2 +

heading 2

+ ${' \t\n'} +
+ `); + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + const owned = aria.getOwnedVirtual(target); assert.lengthOf(owned, 5); - assert.equal(owned[0].actualNode.textContent, 'text 1'); - assert.equal(owned[1].actualNode.textContent, 'heading 1'); - assert.equal(owned[2].actualNode.textContent, 'text 2'); - assert.equal(owned[3].actualNode.textContent, 'heading 2'); - assert.equal(owned[4].actualNode.textContent, ' \t\n'); + assert.include(owned[0].actualNode.textContent, 'text 1'); + assert.include(owned[1].actualNode.textContent, 'heading 1'); + assert.include(owned[2].actualNode.textContent, 'text 2'); + assert.include(owned[3].actualNode.textContent, 'heading 2'); + assert.include(owned[4].actualNode.textContent, ' \t\n'); }); - it('returns an empty array if there are no owned elements', function () { + it('returns an empty array if there are no owned elements', () => { fixtureSetup('
'); - var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; - var owned = aria.getOwnedVirtual(target); + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + const owned = aria.getOwnedVirtual(target); assert.lengthOf(owned, 0); }); }); diff --git a/test/commons/aria/get-role-type.js b/test/commons/aria/get-role-type.js index 6b6a4ea4d..16a9d05fa 100644 --- a/test/commons/aria/get-role-type.js +++ b/test/commons/aria/get-role-type.js @@ -1,9 +1,8 @@ -describe('aria.getRoleType', function () { - 'use strict'; - var queryFixture = axe.testUtils.queryFixture; - var getRoleType = axe.commons.aria.getRoleType; +describe('aria.getRoleType', () => { + const queryFixture = axe.testUtils.queryFixture; + const getRoleType = axe.commons.aria.getRoleType; - beforeEach(function () { + beforeEach(() => { axe._load({}); axe.configure({ standards: { @@ -16,33 +15,33 @@ describe('aria.getRoleType', function () { }); }); - afterEach(function () { + afterEach(() => { axe.reset(); }); - it('should return the type from the lookup table', function () { + it('should return the type from the lookup table', () => { assert.equal(getRoleType('cats'), 'stuff'); }); - it('should return null if role is not found in the lookup table', function () { + it('should return null if role is not found in the lookup table', () => { assert.isNull(getRoleType('dogs')); }); - it('should return null when passed null', function () { + it('should return null when passed null', () => { assert.isNull(getRoleType(null)); }); - it('should return null when passed undefined', function () { + it('should return null when passed undefined', () => { assert.isNull(getRoleType(undefined)); }); - it('returns the type from the role of a virtual node', function () { - var vNode = queryFixture(''); + it('returns the type from the role of a virtual node', () => { + const vNode = queryFixture(''); assert.equal(getRoleType(vNode), 'stuff'); }); - it('returns the type from the role of a DOM node', function () { - var domNode = queryFixture( + it('returns the type from the role of a DOM node', () => { + const domNode = queryFixture( '' ).actualNode; assert.equal(getRoleType(domNode), 'stuff'); diff --git a/test/commons/aria/get-role.js b/test/commons/aria/get-role.js index 340c90a88..deccbff54 100644 --- a/test/commons/aria/get-role.js +++ b/test/commons/aria/get-role.js @@ -1,367 +1,366 @@ -describe('aria.getRole', function () { - 'use strict'; - var aria = axe.commons.aria; - var flatTreeSetup = axe.testUtils.flatTreeSetup; - var fixture = document.querySelector('#fixture'); +describe('aria.getRole', () => { + const aria = axe.commons.aria; + const flatTreeSetup = axe.testUtils.flatTreeSetup; + const fixture = document.querySelector('#fixture'); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('returns valid roles', function () { - var node = document.createElement('div'); + it('returns valid roles', () => { + const node = document.createElement('div'); node.setAttribute('role', 'button'); flatTreeSetup(node); assert.equal(aria.getRole(node), 'button'); }); - it('handles case sensitivity', function () { - var node = document.createElement('div'); + it('handles case sensitivity', () => { + const node = document.createElement('div'); node.setAttribute('role', 'BUTTON'); flatTreeSetup(node); assert.equal(aria.getRole(node), 'button'); }); - it('handles whitespacing', function () { - var node = document.createElement('div'); + it('handles whitespacing', () => { + const node = document.createElement('div'); node.setAttribute('role', ' button '); flatTreeSetup(node); assert.equal(aria.getRole(node), 'button'); }); - it('returns null when there is no role', function () { - var node = document.createElement('div'); + it('returns null when there is no role', () => { + const node = document.createElement('div'); flatTreeSetup(node); assert.isNull(aria.getRole(node)); }); - it('returns the explit role if it is valid and non-abstract', function () { - var node = document.createElement('li'); + it('returns the explit role if it is valid and non-abstract', () => { + const node = document.createElement('li'); node.setAttribute('role', 'menuitem'); flatTreeSetup(node); assert.equal(aria.getRole(node), 'menuitem'); }); - it('returns the implicit role if the explicit is invalid', function () { + it('returns the implicit role if the explicit is invalid', () => { fixture.innerHTML = '
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'listitem'); }); - it('ignores fallback roles by default', function () { - var node = document.createElement('div'); + it('ignores fallback roles by default', () => { + const node = document.createElement('div'); node.setAttribute('role', 'spinbutton button'); flatTreeSetup(node); assert.isNull(aria.getRole(node)); }); - it('accepts virtualNode objects', function () { - var node = document.createElement('div'); + it('accepts virtualNode objects', () => { + const node = document.createElement('div'); node.setAttribute('role', 'button'); - var vNode = flatTreeSetup(node)[0]; + const vNode = flatTreeSetup(node)[0]; assert.equal(aria.getRole(vNode), 'button'); }); - it('returns null if the node is not an element', function () { - var node = document.createTextNode('foo bar baz'); + it('returns null if the node is not an element', () => { + const node = document.createTextNode('foo bar baz'); flatTreeSetup(node); assert.isNull(aria.getRole(node)); }); - it('runs role resolution with role=none', function () { + it('runs role resolution with role=none', () => { fixture.innerHTML = '
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'listitem'); }); - it('runs role resolution with role=presentation', function () { + it('runs role resolution with role=presentation', () => { fixture.innerHTML = '
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'listitem'); }); - it('handles focusable element with role="none"', function () { + it('handles focusable element with role="none"', () => { fixture.innerHTML = ''; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'button'); }); - describe('presentational role inheritance', function () { - it('handles presentation role inheritance for ul', function () { + describe('presentational role inheritance', () => { + it('handles presentation role inheritance for ul', () => { fixture.innerHTML = '
  • foo
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'presentation'); }); - it('handles presentation role inheritance for ol', function () { + it('handles presentation role inheritance for ol', () => { fixture.innerHTML = '
  1. foo
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'presentation'); }); - it('handles presentation role inheritance for dt', function () { + it('handles presentation role inheritance for dt', () => { fixture.innerHTML = '
foo
bar>
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'presentation'); }); - it('handles presentation role inheritance for dd', function () { + it('handles presentation role inheritance for dd', () => { fixture.innerHTML = '
foo
bar>
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'presentation'); }); - it('handles presentation role inheritance for dt with div wrapper', function () { + it('handles presentation role inheritance for dt with div wrapper', () => { fixture.innerHTML = '
foo
bar>
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'presentation'); }); - it('handles presentation role inheritance for dd with div wrapper', function () { + it('handles presentation role inheritance for dd with div wrapper', () => { fixture.innerHTML = '
foo
bar>
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'presentation'); }); - it('handles presentation role inheritance for thead', function () { + it('handles presentation role inheritance for thead', () => { fixture.innerHTML = '
higoodbye
hifoo
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'presentation'); }); - it('handles presentation role inheritance for td', function () { + it('handles presentation role inheritance for td', () => { fixture.innerHTML = '
higoodbye
hifoo
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'presentation'); }); - it('handles presentation role inheritance for th', function () { + it('handles presentation role inheritance for th', () => { fixture.innerHTML = '
higoodbye
hifoo
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'presentation'); }); - it('handles presentation role inheritance for tbody', function () { + it('handles presentation role inheritance for tbody', () => { fixture.innerHTML = '
higoodbye
hifoo
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'presentation'); }); - it('handles presentation role inheritance for tr', function () { + it('handles presentation role inheritance for tr', () => { fixture.innerHTML = '
higoodbye
hifoo
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'presentation'); }); - it('handles presentation role inheritance for tfoot', function () { + it('handles presentation role inheritance for tfoot', () => { fixture.innerHTML = '
higoodbye
hifoo
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'presentation'); }); - it('returns implicit role for presentation role inheritance if ancestor is not the required ancestor', function () { + it('returns implicit role for presentation role inheritance if ancestor is not the required ancestor', () => { fixture.innerHTML = '
  • foo
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'listitem'); }); - it('does not override explicit role with presentation role inheritance', function () { + it('does not override explicit role with presentation role inheritance', () => { fixture.innerHTML = '
  • foo
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'listitem'); }); - it('does not continue presentation role with explicit role in between', function () { + it('does not continue presentation role with explicit role in between', () => { fixture.innerHTML = '
foo
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'cell'); }); - it('handles presentation role inheritance with invalid role in between', function () { + it('handles presentation role inheritance with invalid role in between', () => { fixture.innerHTML = '
foo
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'presentation'); }); - it('does not continue presentation role through nested layers', function () { + it('does not continue presentation role through nested layers', () => { fixture.innerHTML = '
    • foo
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'listitem'); }); - it('throws an error if the tree is incomplete', function () { + it('throws an error if the tree is incomplete', () => { fixture.innerHTML = '
  • foo
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(node); - assert.throws(function () { + assert.throws(() => { aria.getRole(node); }); }); }); - describe('noImplicit', function () { - it('returns the implicit role by default', function () { + describe('noImplicit', () => { + it('returns the implicit role by default', () => { fixture.innerHTML = '
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'listitem'); }); - it('returns null rather than the implicit role with `noImplicit: true`', function () { - var node = document.createElement('li'); + it('returns null rather than the implicit role with `noImplicit: true`', () => { + const node = document.createElement('li'); flatTreeSetup(node); assert.isNull(aria.getRole(node, { noImplicit: true })); }); - it('does not do role resolution if noImplicit: true', function () { - var node = document.createElement('li'); + it('does not do role resolution if noImplicit: true', () => { + const node = document.createElement('li'); node.setAttribute('role', 'none'); node.setAttribute('aria-label', 'foo'); flatTreeSetup(node); assert.equal(aria.getRole(node, { noImplicit: true }), null); }); - it('still returns the explicit role', function () { - var node = document.createElement('li'); + it('still returns the explicit role', () => { + const node = document.createElement('li'); node.setAttribute('role', 'button'); flatTreeSetup(node); assert.equal(aria.getRole(node, { noImplicit: true }), 'button'); }); - it('returns the implicit role with `noImplicit: false`', function () { + it('returns the implicit role with `noImplicit: false`', () => { fixture.innerHTML = '
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node, { noImplicit: false }), 'listitem'); }); }); - describe('abstracts', function () { - it('ignores abstract roles by default', function () { + describe('abstracts', () => { + it('ignores abstract roles by default', () => { fixture.innerHTML = '
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node), 'listitem'); }); - it('returns abstract roles with `abstracts: true`', function () { - var node = document.createElement('li'); + it('returns abstract roles with `abstracts: true`', () => { + const node = document.createElement('li'); node.setAttribute('role', 'section'); flatTreeSetup(node); assert.equal(aria.getRole(node, { abstracts: true }), 'section'); }); - it('does not returns abstract roles with `abstracts: false`', function () { + it('does not returns abstract roles with `abstracts: false`', () => { fixture.innerHTML = '
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node, { abstracts: false }), 'listitem'); }); }); - describe('dpub', function () { - it('ignores DPUB roles by default', function () { - var node = document.createElement('section'); + describe('dpub', () => { + it('ignores DPUB roles by default', () => { + const node = document.createElement('section'); node.setAttribute('role', 'doc-chapter'); flatTreeSetup(node); assert.isNull(aria.getRole(node)); }); - it('returns DPUB roles with `dpub: true`', function () { - var node = document.createElement('section'); + it('returns DPUB roles with `dpub: true`', () => { + const node = document.createElement('section'); node.setAttribute('role', 'doc-chapter'); flatTreeSetup(node); assert.equal(aria.getRole(node, { dpub: true }), 'doc-chapter'); }); - it('does not returns DPUB roles with `dpub: false`', function () { - var node = document.createElement('section'); + it('does not returns DPUB roles with `dpub: false`', () => { + const node = document.createElement('section'); node.setAttribute('role', 'doc-chapter'); flatTreeSetup(node); assert.isNull(aria.getRole(node, { dpub: false })); }); }); - describe('fallback', function () { - it('returns the first valid item in the list', function () { - var node = document.createElement('div'); + describe('fallback', () => { + it('returns the first valid item in the list', () => { + const node = document.createElement('div'); node.setAttribute('role', 'link button'); flatTreeSetup(node); assert.equal(aria.getRole(node, { fallback: true }), 'link'); }); - it('skips over invalid roles', function () { - var node = document.createElement('div'); + it('skips over invalid roles', () => { + const node = document.createElement('div'); node.setAttribute('role', 'foobar button'); flatTreeSetup(node); assert.equal(aria.getRole(node, { fallback: true }), 'button'); }); - it('returns the null if all roles are invalid and there is no implicit role', function () { - var node = document.createElement('div'); + it('returns the null if all roles are invalid and there is no implicit role', () => { + const node = document.createElement('div'); node.setAttribute('role', 'foo bar baz'); flatTreeSetup(node); assert.isNull(aria.getRole(node, { fallback: true })); }); - it('respects the defaults', function () { + it('respects the defaults', () => { fixture.innerHTML = '
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.equal(aria.getRole(node, { fallback: true }), 'listitem'); }); - it('respect the `noImplicit` option', function () { - var node = document.createElement('li'); + it('respect the `noImplicit` option', () => { + const node = document.createElement('li'); node.setAttribute('role', 'doc-chapter section'); flatTreeSetup(node); assert.isNull(aria.getRole(node, { fallback: true, noImplicit: true })); }); - it('respect the `abstracts` option', function () { - var node = document.createElement('li'); + it('respect the `abstracts` option', () => { + const node = document.createElement('li'); node.setAttribute('role', 'doc-chapter section'); flatTreeSetup(node); assert.equal( @@ -370,8 +369,8 @@ describe('aria.getRole', function () { ); }); - it('respect the `dpub` option', function () { - var node = document.createElement('li'); + it('respect the `dpub` option', () => { + const node = document.createElement('li'); node.setAttribute('role', 'doc-chapter section'); flatTreeSetup(node); assert.equal( @@ -381,41 +380,41 @@ describe('aria.getRole', function () { }); }); - describe('noPresentational is honored', function () { - it('handles no inheritance role = presentation', function () { + describe('noPresentational is honored', () => { + it('handles no inheritance role = presentation', () => { fixture.innerHTML = ''; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.isNull(aria.getRole(node, { noPresentational: true })); }); - it('handles inheritance role = presentation', function () { + it('handles inheritance role = presentation', () => { fixture.innerHTML = '
  • foo
'; flatTreeSetup(fixture); - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); assert.isNull(aria.getRole(node, { noPresentational: true })); }); - it('handles implicit role', function () { - var node = document.createElement('img'); + it('handles implicit role', () => { + const node = document.createElement('img'); node.setAttribute('alt', ''); flatTreeSetup(node); assert.isNull(aria.getRole(node, { noPresentational: true })); }); - it('handles role = none', function () { - var node = document.createElement('div'); + it('handles role = none', () => { + const node = document.createElement('div'); node.setAttribute('role', 'none'); flatTreeSetup(node); assert.isNull(aria.getRole(node, { noPresentational: true })); }); }); - describe('SerialVirtualNode', function () { - it('works with the SerialVirtualNode', function () { - var vNode = new axe.SerialVirtualNode({ + describe('SerialVirtualNode', () => { + it('works with the SerialVirtualNode', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'div', attributes: { role: 'button' @@ -424,12 +423,12 @@ describe('aria.getRole', function () { assert.equal(aria.getRole(vNode), 'button'); }); - it('does not throw for missing parent in presentational role inheritance', function () { - var vNode = new axe.SerialVirtualNode({ + it('does not throw for missing parent in presentational role inheritance', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'li' }); - assert.doesNotThrow(function () { + assert.doesNotThrow(() => { assert.equal(aria.getRole(vNode), 'listitem'); }); }); diff --git a/test/commons/aria/get-roles-by-type.js b/test/commons/aria/get-roles-by-type.js index 0174b2da3..f3b24b724 100644 --- a/test/commons/aria/get-roles-by-type.js +++ b/test/commons/aria/get-roles-by-type.js @@ -1,15 +1,13 @@ -describe('aria.getRolesByType', function () { - 'use strict'; - - before(function () { +describe('aria.getRolesByType', () => { + before(() => { axe._load({}); }); - afterEach(function () { + afterEach(() => { axe.reset(); }); - it('should return array if roletype is found in the lookup table', function () { + it('should return array if roletype is found in the lookup table', () => { axe.configure({ standards: { ariaRoles: { @@ -25,7 +23,7 @@ describe('aria.getRolesByType', function () { assert.deepEqual(axe.commons.aria.getRolesByType('stuff'), ['cats']); }); - it('should return empty array if role is not found in the lookup table', function () { + it('should return empty array if role is not found in the lookup table', () => { assert.deepEqual(axe.commons.aria.getRolesByType('blahblahblah'), []); }); }); diff --git a/test/commons/aria/get-roles-with-name-from-contents.js b/test/commons/aria/get-roles-with-name-from-contents.js index fc5b56fbe..a2f003116 100644 --- a/test/commons/aria/get-roles-with-name-from-contents.js +++ b/test/commons/aria/get-roles-with-name-from-contents.js @@ -1,15 +1,13 @@ -describe('aria.getRolesWithNameFromContents', function () { - 'use strict'; - - before(function () { +describe('aria.getRolesWithNameFromContents', () => { + before(() => { axe._load({}); }); - afterEach(function () { + afterEach(() => { axe.reset(); }); - it('should return array if nameFrom contents is found in the lookup table', function () { + it('should return array if nameFrom contents is found in the lookup table', () => { axe.configure({ standards: { ariaRoles: { diff --git a/test/commons/aria/implicit-role.js b/test/commons/aria/implicit-role.js index 03b6ed56f..955e5e2f1 100644 --- a/test/commons/aria/implicit-role.js +++ b/test/commons/aria/implicit-role.js @@ -1,468 +1,581 @@ -describe('aria.implicitRole', function () { - 'use strict'; - var implicitRole = axe.commons.aria.implicitRole; - var flatTreeSetup = axe.testUtils.flatTreeSetup; - var fixture = document.querySelector('#fixture'); - - afterEach(function () { - fixture.innerHTML = ''; - }); +describe('aria.implicitRole', () => { + const html = axe.testUtils.html; + const implicitRole = axe.commons.aria.implicitRole; + const flatTreeSetup = axe.testUtils.flatTreeSetup; + const fixture = document.querySelector('#fixture'); + const queryFixture = axe.testUtils.queryFixture; // test string role (don't need to test all of them just that // one works) - it('should return button for button', function () { + it('should return button for button', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'button'); }); - it('should error if element is not in the tree', function () { + it('should error if element is not in the tree', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); - assert.throws(function () { + const node = fixture.querySelector('#target'); + assert.throws(() => { implicitRole(node); }); }); - it('should return null if there is no implicit role', function () { + it('should return null if there is no implicit role', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.isNull(implicitRole(node)); }); - it('should return null if there is no implicit role when not considering chromium', function () { + it('should return null if there is no implicit role when not considering chromium', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.isNull(implicitRole(node)); }); - it('should return the chromium implicit role for elements that have one', function () { + it('should return the chromium implicit role for elements that have one', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node, { chromium: true }), 'Canvas'); }); - it('should return link for "a[href]"', function () { + it('should return link for "a[href]"', () => { fixture.innerHTML = 'link'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'link'); }); - it('should return null for "a:not([href])"', function () { + it('should return null for "a:not([href])"', () => { fixture.innerHTML = 'link'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.isNull(implicitRole(node)); }); - it('should return link for "area[href]"', function () { + it('should return link for "area[href]"', () => { fixture.innerHTML = 'link'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'link'); }); - it('should return null for "area:not([href])"', function () { + it('should return null for "area:not([href])"', () => { fixture.innerHTML = 'link'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.isNull(implicitRole(node)); }); - it('should return contentinfo for "body footer"', function () { + it('should return contentinfo for "body footer"', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'contentinfo'); }); - it('should return null for footer with sectioning parent', function () { - var nodes = ['article', 'aside', 'main', 'nav', 'section']; - var roles = ['article', 'complementary', 'main', 'navigation', 'region']; + it('should return null for footer with sectioning or main parent', () => { + const nodes = ['article', 'aside', 'main', 'nav', 'section']; + const roles = ['article', 'complementary', 'main', 'navigation', 'region']; for (var i = 0; i < nodes.length; i++) { - fixture.innerHTML = - '<' + nodes[i] + '>
'; + fixture.innerHTML = html`<${nodes[i]}>
`; var node = fixture.querySelector('#target'); flatTreeSetup(fixture); - assert.isNull(implicitRole(node), nodes[i] + ' not null'); + assert.isNull(implicitRole(node), `${nodes[i]} not null`); } for (var i = 0; i < roles.length; i++) { - fixture.innerHTML = - '
'; + fixture.innerHTML = html`
+
+
`; var node = fixture.querySelector('#target'); flatTreeSetup(fixture); - assert.isNull(implicitRole(node), '[' + roles[i] + '] not null'); + assert.isNull(implicitRole(node), `[${roles[i]}] not null`); } }); - it('should return form for form with accessible name aria-label', function () { + it('should return form for form with accessible name aria-label', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'form'); }); - it('should return form for form with accessible name aria-labelledby', function () { + it('should return form for form with accessible name aria-labelledby', () => { fixture.innerHTML = '
foo
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'form'); }); - it('should return null for form with accessible name title', function () { + it('should return null for form with accessible name title', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.isNull(implicitRole(node)); }); - it('should return null for form without accessible name', function () { + it('should return null for form without accessible name', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.isNull(implicitRole(node)); }); - it('should return banner for "body header"', function () { + it('should return complementary for aside scoped to body', () => { + fixture.innerHTML = ''; + const node = fixture.querySelector('#target'); + flatTreeSetup(fixture); + assert.equal(implicitRole(node), 'complementary'); + }); + + it('should return complementary for aside scoped to main', () => { + fixture.innerHTML = '
'; + const node = fixture.querySelector('#target'); + flatTreeSetup(fixture); + assert.equal(implicitRole(node), 'complementary'); + }); + + it('should return complementary for aside scoped to element with role=main', () => { + fixture.innerHTML = + '
'; + const node = fixture.querySelector('#target'); + flatTreeSetup(fixture); + assert.equal(implicitRole(node), 'complementary'); + }); + + it('should return null for aside with sectioning parent', () => { + const nodes = ['article', 'aside', 'nav', 'section']; + const roles = ['article', 'complementary', 'navigation', 'region']; + + for (var i = 0; i < nodes.length; i++) { + fixture.innerHTML = html`<${nodes[i]}>
`; + var node = fixture.querySelector('#target'); + flatTreeSetup(fixture); + assert.isNull(implicitRole(node), `${nodes[i]} not null`); + } + + for (var i = 0; i < roles.length; i++) { + fixture.innerHTML = html`
+
+
`; + var node = fixture.querySelector('#target'); + flatTreeSetup(fixture); + assert.isNull(implicitRole(node), `[${roles[i]}] not null`); + } + }); + + it('should return complementary for aside with sectioning parent if aside has aria-label', () => { + const nodes = ['article', 'aside', 'nav', 'section']; + const roles = ['article', 'complementary', 'navigation', 'region']; + + for (var i = 0; i < nodes.length; i++) { + fixture.innerHTML = html`<${nodes[i]}>`; + var node = fixture.querySelector('#target'); + flatTreeSetup(fixture); + assert.equal(implicitRole(node), 'complementary'); + } + + for (var i = 0; i < roles.length; i++) { + fixture.innerHTML = html`
+ +
`; + var node = fixture.querySelector('#target'); + flatTreeSetup(fixture); + assert.equal(implicitRole(node), 'complementary'); + } + }); + + it('should return null for sectioned aside with empty aria-label', () => { + fixture.innerHTML = + '
'; + const node = fixture.querySelector('#target'); + flatTreeSetup(fixture); + assert.isNull(implicitRole(node)); + }); + + it('should return complementary for sectioned aside with title', () => { + fixture.innerHTML = + '
'; + const node = fixture.querySelector('#target'); + flatTreeSetup(fixture); + assert.equal(implicitRole(node), 'complementary'); + }); + + it('should return null for sectioned aside with empty title', () => { + fixture.innerHTML = + '
'; + const node = fixture.querySelector('#target'); + flatTreeSetup(fixture); + assert.isNull(implicitRole(node)); + }); + + it('should return banner for "body header"', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'banner'); }); - it('should return null for header with sectioning parent', function () { - var nodes = ['article', 'aside', 'main', 'nav', 'section']; - var roles = ['article', 'complementary', 'main', 'navigation', 'region']; + it('should return null for header with sectioning or main parent', () => { + const nodes = ['article', 'aside', 'main', 'nav', 'section']; + const roles = ['article', 'complementary', 'main', 'navigation', 'region']; for (var i = 0; i < nodes.length; i++) { - fixture.innerHTML = - '<' + nodes[i] + '>
'; + fixture.innerHTML = html`<${nodes[i]}>
`; var node = fixture.querySelector('#target'); flatTreeSetup(fixture); - assert.isNull(implicitRole(node), nodes[i] + ' not null'); + assert.isNull(implicitRole(node), `${nodes[i]} not null`); } for (var i = 0; i < roles.length; i++) { - fixture.innerHTML = - '
'; + fixture.innerHTML = html`
+
+
`; var node = fixture.querySelector('#target'); flatTreeSetup(fixture); - assert.isNull(implicitRole(node), '[' + roles[i] + '] not null'); + assert.isNull(implicitRole(node), `[${roles[i]}] not null`); } }); - it('should return img for "img[alt]"', function () { + it('should return img for "img[alt]"', () => { fixture.innerHTML = 'value'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'img'); }); - it('should return img for "img:not([alt])"', function () { + it('should return img for "img:not([alt])"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'img'); }); - it('should return presentation for "img" with empty alt', function () { + it('should return presentation for "img" with empty alt', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'presentation'); }); - it('should return img for "img" with empty alt and global aria attribute', function () { + it('should return img for "img" with empty alt and global aria attribute', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'img'); }); - it('should return img for "img" with empty alt and focusable', function () { + it('should return img for "img" with empty alt and focusable', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'img'); }); - it('should return button for "input[type=button]"', function () { + it('should return button for "input[type=button]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'button'); }); - it('should return button for "input[type=image]"', function () { + it('should return button for "input[type=image]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'button'); }); - it('should return button for "input[type=reset]"', function () { + it('should return button for "input[type=reset]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'button'); }); - it('should return button for "input[type=submit]"', function () { + it('should return button for "input[type=submit]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'button'); }); - it('should return checkbox for "input[type=checkbox]"', function () { + it('should return checkbox for "input[type=checkbox]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'checkbox'); }); - it('should return textbox for "input[type=email]"', function () { + it('should return textbox for "input[type=email]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'textbox'); }); - it('should return textbox for "input[type=tel]"', function () { + it('should return textbox for "input[type=tel]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'textbox'); }); - it('should return textbox for "input[type=text]"', function () { + it('should return textbox for "input[type=text]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'textbox'); }); - it('should return textbox for "input[type=url]"', function () { + it('should return textbox for "input[type=url]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'textbox'); }); - it('should return textbox for "input[type=password]"', function () { + it('should return textbox for "input[type=password]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'textbox'); }); - it('should return textbox for "input[type=time]"', function () { + it('should return textbox for "input[type=time]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'textbox'); }); - it('should return textbox for "input[type=date]"', function () { + it('should return textbox for "input[type=date]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'textbox'); }); - it('should return textbox for "input:not([type])"', function () { + it('should return textbox for "input:not([type])"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'textbox'); }); - it('should return combobox for "input[list]" that points to a datalist', function () { + it('should return combobox for "input[list]" that points to a datalist', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'combobox'); }); - it('should return textbox for "input[list]" that does not point to a datalist', function () { + it('should return textbox for "input[list]" that does not point to a datalist', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'textbox'); }); - it('should return textbox for "input[type=password][list]"', function () { - fixture.innerHTML = - '' + - ''; - var node = fixture.querySelector('#target'); + it('should return textbox for "input[type=password][list]"', () => { + fixture.innerHTML = html` + + + `; + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'textbox'); }); - it('should return spinbutton for "input[type=number]"', function () { + it('should return spinbutton for "input[type=number]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'spinbutton'); }); - it('should return radio for "input[type=radio]"', function () { + it('should return radio for "input[type=radio]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'radio'); }); - it('should return slider for "input[type=range]"', function () { + it('should return slider for "input[type=range]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'slider'); }); - it('should return searchbox for "input[type=search]"', function () { + it('should return searchbox for "input[type=search]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'searchbox'); }); - it('should return combobox for "input[type=search][list]"', function () { + it('should return combobox for "input[type=search][list]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'combobox'); }); - it('should return textbox for "input[type=invalid]"', function () { + it('should return textbox for "input[type=invalid]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'textbox'); }); - it('should return region for "section" with accessible name aria-label', function () { + it('should return region for "section" with accessible name aria-label', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'region'); }); - it('should return region for section with accessible name aria-labelledby', function () { + it('should return region for section with accessible name aria-labelledby', () => { fixture.innerHTML = '
foo
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'region'); }); - it('should return null for section with accessible name title', function () { + it('should return null for section with accessible name title', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.isNull(implicitRole(node)); }); - it('should return null for "section" without accessible name', function () { + it('should return null for "section" without accessible name', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.isNull(implicitRole(node)); }); - it('should return null for "section" with empty aria-label', function () { + it('should return null for "section" with empty aria-label', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.isNull(implicitRole(node)); }); - it('should return null for "section" with empty aria-labelledby', function () { + it('should return null for "section" with empty aria-labelledby', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.isNull(implicitRole(node)); }); - it('should return null for "section" with empty title', function () { + it('should return null for "section" with empty title', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.isNull(implicitRole(node)); }); - it('should return listbox for "select[multiple]"', function () { + it('should return listbox for "select[multiple]"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'listbox'); }); - it('should return listbox for "select[size]" > 1', function () { + it('should return listbox for "select[size]" > 1', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'listbox'); }); - it('should return combobox for "select[size]" <= 1', function () { + it('should return combobox for "select[size]" <= 1', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'combobox'); }); - it('should return combobox for "select"', function () { + it('should return combobox for "select"', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'combobox'); }); - it('should return cell for "td"', function () { + it('should return cell for "td"', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'cell'); }); - it('should return gridcell for "td" with grid parent', function () { + it('should return gridcell for "td" with grid parent', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'gridcell'); }); - it('should return gridcell for "td" with treegrid parent', function () { + it('should return gridcell for "td" with treegrid parent', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'gridcell'); }); - it('should return rowheader for "th[scope=row]"', function () { + it('should return rowheader for "th[scope=row]"', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'rowheader'); }); - it('should return columnheader for "th[scope=col]"', function () { + it('should return columnheader for "th[scope=col]"', () => { fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); flatTreeSetup(fixture); assert.equal(implicitRole(node), 'columnheader'); }); + + describe('ElementInternals', () => { + it('returns element internals role', () => { + const vNode = queryFixture( + '' + ); + assert.equal(implicitRole(vNode), 'button'); + }); + + it('returns null for element without internals', () => { + const vNode = queryFixture( + '' + ); + delete vNode.actualNode._internals; + assert.isNull(implicitRole(vNode)); + }); + + it('returns null for element without internals role', () => { + const vNode = queryFixture( + '' + ); + vNode.actualNode._internals.role = undefined; + assert.isNull(implicitRole(vNode)); + }); + + // no test is needed for returning internals role over implicit html role since only custom-elements can have element internals + }); }); diff --git a/test/commons/aria/is-accessible-ref.js b/test/commons/aria/is-accessible-ref.js index 91394cab6..6e7c48e49 100644 --- a/test/commons/aria/is-accessible-ref.js +++ b/test/commons/aria/is-accessible-ref.js @@ -1,9 +1,7 @@ -describe('aria.isAccessibleRef', function () { - 'use strict'; - - var fixture = document.getElementById('fixture'); - var isAccessibleRef = axe.commons.aria.isAccessibleRef; - var shadowSupport = axe.testUtils.shadowSupport.v1; +describe('aria.isAccessibleRef', () => { + const fixture = document.getElementById('fixture'); + const isAccessibleRef = axe.commons.aria.isAccessibleRef; + const shadowSupport = axe.testUtils.shadowSupport.v1; function setLookup(attrs) { axe.configure({ @@ -13,66 +11,68 @@ describe('aria.isAccessibleRef', function () { }); } - before(function () { + before(() => { axe._load({}); }); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; axe.reset(); }); - it('returns false by default', function () { + it('returns false by default', () => { fixture.innerHTML = '
'; - var node = document.getElementById('foo'); + const node = document.getElementById('foo'); assert.isFalse(isAccessibleRef(node)); }); - it('returns true for IDs used in aria IDREF attributes', function () { + it('returns true for IDs used in aria IDREF attributes', () => { setLookup({ 'aria-foo': { type: 'idref' } }); fixture.innerHTML = '
'; - var node = document.getElementById('foo'); + const node = document.getElementById('foo'); assert.isTrue(isAccessibleRef(node)); }); - it('returns true for IDs used in aria IDREFS attributes', function () { + it('returns true for IDs used in aria IDREFS attributes', () => { setLookup({ 'aria-bar': { type: 'idrefs' } }); fixture.innerHTML = '
'; - var node1 = document.getElementById('foo'); - var node2 = document.getElementById('bar'); + const node1 = document.getElementById('foo'); + const node2 = document.getElementById('bar'); assert.isTrue(isAccessibleRef(node1)); assert.isTrue(isAccessibleRef(node2)); }); - it('returns true for IDs used in label[for] attributes', function () { + it('returns true for IDs used in label[for] attributes', () => { setLookup({ 'aria-foo': { type: 'idref' } }); fixture.innerHTML = ''; - var node = document.getElementById('baz'); + const node = document.getElementById('baz'); assert.isTrue(isAccessibleRef(node)); }); - (shadowSupport ? it : xit)('works inside shadow DOM', function () { + (shadowSupport ? it : xit)('works inside shadow DOM', () => { setLookup({ 'aria-bar': { type: 'idref' } }); fixture.innerHTML = '
'; - var shadow = document.getElementById('foo').attachShadow({ mode: 'open' }); + const shadow = document + .getElementById('foo') + .attachShadow({ mode: 'open' }); shadow.innerHTML = '
'; - var node = shadow.getElementById('bar'); + const node = shadow.getElementById('bar'); assert.isTrue(isAccessibleRef(node)); }); (shadowSupport ? it : xit)( 'returns false for IDREFs inside shadow DOM', - function () { + () => { setLookup({ 'aria-foo': { type: 'idrefs' } }); fixture.innerHTML = '
'; - var node1 = document.getElementById('foo'); - var node2 = document.getElementById('bar'); + const node1 = document.getElementById('foo'); + const node2 = document.getElementById('bar'); - var shadow = node1.attachShadow({ mode: 'open' }); + const shadow = node1.attachShadow({ mode: 'open' }); shadow.innerHTML = '
'; assert.isFalse(isAccessibleRef(node1)); @@ -82,17 +82,17 @@ describe('aria.isAccessibleRef', function () { (shadowSupport ? it : xit)( 'returns false for IDREFs outside shadow DOM', - function () { + () => { setLookup({ 'aria-bar': { type: 'idref' } }); fixture.innerHTML = '
'; - var shadow = document + const shadow = document .getElementById('foo') .attachShadow({ mode: 'open' }); shadow.innerHTML = '
'; - var node = shadow.getElementById('bar'); + const node = shadow.getElementById('bar'); assert.isFalse(isAccessibleRef(node)); } ); diff --git a/test/commons/aria/is-aria-role-allowed-on-element.js b/test/commons/aria/is-aria-role-allowed-on-element.js index 5a884f07e..851c45ea1 100644 --- a/test/commons/aria/is-aria-role-allowed-on-element.js +++ b/test/commons/aria/is-aria-role-allowed-on-element.js @@ -1,226 +1,225 @@ -describe('aria.isAriaRoleAllowedOnElement', function () { - 'use strict'; - var flatTreeSetup = axe.testUtils.flatTreeSetup; +describe('aria.isAriaRoleAllowedOnElement', () => { + const flatTreeSetup = axe.testUtils.flatTreeSetup; - it('returns true for SECTION with role alert', function () { - var node = document.createElement('section'); - var role = 'alert'; + it('returns true for SECTION with role alert', () => { + const node = document.createElement('section'); + const role = 'alert'; node.setAttribute('role', role); flatTreeSetup(node); - var actual = axe.commons.aria.isAriaRoleAllowedOnElement(node, role); - var expected = true; + const actual = axe.commons.aria.isAriaRoleAllowedOnElement(node, role); + const expected = true; assert.equal(actual, expected); }); - it('returns false for SECTION with role checkbox', function () { - var node = document.createElement('section'); - var role = 'checkbox'; + it('returns false for SECTION with role checkbox', () => { + const node = document.createElement('section'); + const role = 'checkbox'; node.setAttribute('role', role); flatTreeSetup(node); - var actual = axe.commons.aria.isAriaRoleAllowedOnElement(node, role); - var expected = false; + const actual = axe.commons.aria.isAriaRoleAllowedOnElement(node, role); + const expected = false; assert.equal(actual, expected); }); - it('returns true for SVG with role alertdialog', function () { - var node = document.createElement('svg'); - var role = 'alertdialog'; + it('returns true for SVG with role alertdialog', () => { + const node = document.createElement('svg'); + const role = 'alertdialog'; node.setAttribute('role', role); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true for OBJECT with role application', function () { - var node = document.createElement('object'); - var role = 'application'; + it('returns true for OBJECT with role application', () => { + const node = document.createElement('object'); + const role = 'application'; node.setAttribute('role', role); flatTreeSetup(node); - var actual = axe.commons.aria.isAriaRoleAllowedOnElement(node, role); + const actual = axe.commons.aria.isAriaRoleAllowedOnElement(node, role); assert.isTrue(actual); }); - it('returns false for A with role button', function () { - var node = document.createElement('a'); - var role = 'button'; + it('returns false for A with role button', () => { + const node = document.createElement('a'); + const role = 'button'; node.setAttribute('role', role); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns false for ARTICLE with role cell', function () { - var node = document.createElement('article'); - var role = 'cell'; + it('returns false for ARTICLE with role cell', () => { + const node = document.createElement('article'); + const role = 'cell'; node.setAttribute('role', role); flatTreeSetup(node); assert.isFalse(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true for BUTTON with role checkbox', function () { - var node = document.createElement('button'); - var role = 'checkbox'; + it('returns true for BUTTON with role checkbox', () => { + const node = document.createElement('button'); + const role = 'checkbox'; node.setAttribute('role', role); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true for IFRAME with role document', function () { - var node = document.createElement('iframe'); - var role = 'document'; + it('returns true for IFRAME with role document', () => { + const node = document.createElement('iframe'); + const role = 'document'; node.setAttribute('role', role); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true for ASIDE with role feed', function () { - var node = document.createElement('aside'); - var role = 'feed'; + it('returns true for ASIDE with role feed', () => { + const node = document.createElement('aside'); + const role = 'feed'; node.setAttribute('role', role); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true for FIGURE with role group', function () { - var node = document.createElement('figure'); - var role = 'group'; + it('returns true for FIGURE with role group', () => { + const node = document.createElement('figure'); + const role = 'group'; node.setAttribute('role', role); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true for SVG with role img', function () { - var node = document.createElement('svg'); - var role = 'img'; + it('returns true for SVG with role img', () => { + const node = document.createElement('svg'); + const role = 'img'; node.setAttribute('role', role); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true for INPUT with type image and role link', function () { - var node = document.createElement('input'); - var role = 'link'; + it('returns true for INPUT with type image and role link', () => { + const node = document.createElement('input'); + const role = 'link'; node.setAttribute('role', role); node.setAttribute('type', 'image'); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true for HEADER with role none', function () { - var node = document.createElement('header'); - var role = 'none'; + it('returns true for HEADER with role none', () => { + const node = document.createElement('header'); + const role = 'none'; node.setAttribute('role', role); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true for LI with role option', function () { - var node = document.createElement('li'); - var role = 'option'; + it('returns true for LI with role option', () => { + const node = document.createElement('li'); + const role = 'option'; node.setAttribute('role', role); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true for H1 with role tab', function () { - var node = document.createElement('h1'); - var role = 'tab'; + it('returns true for H1 with role tab', () => { + const node = document.createElement('h1'); + const role = 'tab'; node.setAttribute('role', role); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true for OL with role tablist', function () { - var node = document.createElement('ol'); - var role = 'tablist'; + it('returns true for OL with role tablist', () => { + const node = document.createElement('ol'); + const role = 'tablist'; node.setAttribute('role', role); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true when A has namespace as svg and role menuitem', function () { - var node = document.createElementNS('http://www.w3.org/2000/svg', 'a'); + it('returns true when A has namespace as svg and role menuitem', () => { + const node = document.createElementNS('http://www.w3.org/2000/svg', 'a'); flatTreeSetup(node); assert.isTrue( axe.commons.aria.isAriaRoleAllowedOnElement(node, 'menuitem') ); }); - it('returns true when BUTTON has type menu and role as menuitem', function () { - var node = document.createElement('button'); - var role = 'menuitem'; + it('returns true when BUTTON has type menu and role as menuitem', () => { + const node = document.createElement('button'); + const role = 'menuitem'; node.setAttribute('type', 'menu'); node.setAttribute('role', role); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns false when MENU has type context and role navigation', function () { - var node = document.createElement('menu'); - var role = 'navigation'; + it('returns false when MENU has type context and role navigation', () => { + const node = document.createElement('menu'); + const role = 'navigation'; node.setAttribute('type', 'context'); node.setAttribute('role', role); flatTreeSetup(node); assert.isFalse(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true when B has role navigation', function () { - var node = document.createElement('b'); - var role = 'navigation'; + it('returns true when B has role navigation', () => { + const node = document.createElement('b'); + const role = 'navigation'; node.setAttribute('role', role); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true when NAV has role menubar', function () { - var node = document.createElement('nav'); - var role = 'menubar'; + it('returns true when NAV has role menubar', () => { + const node = document.createElement('nav'); + const role = 'menubar'; node.setAttribute('role', role); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true when NAV has role tablist', function () { - var node = document.createElement('nav'); - var role = 'tablist'; + it('returns true when NAV has role tablist', () => { + const node = document.createElement('nav'); + const role = 'tablist'; node.setAttribute('role', role); flatTreeSetup(node); assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns false when PROGRESS has role button', function () { - var node = document.createElement('progress'); - var role = 'button'; + it('returns false when PROGRESS has role button', () => { + const node = document.createElement('progress'); + const role = 'button'; node.setAttribute('role', role); flatTreeSetup(node); assert.isFalse(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); - it('returns true if given element can have any role', function () { - var node = document.createElement('div'); + it('returns true if given element can have any role', () => { + const node = document.createElement('div'); flatTreeSetup(node); - var actual = axe.commons.aria.isAriaRoleAllowedOnElement(node, 'link'); + const actual = axe.commons.aria.isAriaRoleAllowedOnElement(node, 'link'); assert.isTrue(actual); }); - it('returns false if given element cannot have any role', function () { - var node = document.createElement('main'); + it('returns false if given element cannot have any role', () => { + const node = document.createElement('main'); flatTreeSetup(node); - var actual = axe.commons.aria.isAriaRoleAllowedOnElement(node, 'alert'); // changed this + const actual = axe.commons.aria.isAriaRoleAllowedOnElement(node, 'alert'); // changed this assert.isFalse(actual); }); - it('returns false if given element cannot have any role', function () { - var node = document.createElement('track'); + it('returns false if given element cannot have any role', () => { + const node = document.createElement('track'); flatTreeSetup(node); - var actual = axe.commons.aria.isAriaRoleAllowedOnElement(node, 'banner'); + const actual = axe.commons.aria.isAriaRoleAllowedOnElement(node, 'banner'); assert.isFalse(actual); }); - it('returns false if elements implicit role matches the role', function () { - var node = document.createElement('area'); + it('returns false if elements implicit role matches the role', () => { + const node = document.createElement('area'); node.setAttribute('href', '#yay'); node.setAttribute('role', 'link'); flatTreeSetup(node); - var actual = axe.commons.aria.isAriaRoleAllowedOnElement(node, 'link'); + const actual = axe.commons.aria.isAriaRoleAllowedOnElement(node, 'link'); assert.isFalse(actual); }); }); diff --git a/test/commons/aria/is-combobox-popup.js b/test/commons/aria/is-combobox-popup.js index 65ff04ac2..b1b8bc10e 100644 --- a/test/commons/aria/is-combobox-popup.js +++ b/test/commons/aria/is-combobox-popup.js @@ -1,4 +1,5 @@ describe('isComboboxPopup', () => { + const html = axe.testUtils.html; const { isComboboxPopup } = axe.commons.aria; const { queryFixture } = axe.testUtils; @@ -6,8 +7,8 @@ describe('isComboboxPopup', () => { const roles = ['main', 'combobox', 'textbox', 'button']; for (const role of roles) { const vNode = queryFixture( - `
-
` + html`
+
` ); assert.isFalse(isComboboxPopup(vNode)); } @@ -17,8 +18,8 @@ describe('isComboboxPopup', () => { describe(role, () => { it('is false when not related to the combobox', () => { const vNode = queryFixture( - `
-
` + html`
+
` ); assert.isFalse(isComboboxPopup(vNode)); }); @@ -26,24 +27,24 @@ describe('isComboboxPopup', () => { describe('using aria-controls (ARIA 1.2 pattern)', () => { it('is true when referenced', () => { const vNode = queryFixture( - `
-
` + html`
+
` ); assert.isTrue(isComboboxPopup(vNode)); }); it('is false when controlled by a select element', () => { const vNode = queryFixture( - ` -
` + html` +
` ); assert.isFalse(isComboboxPopup(vNode)); }); it('is false when not controlled by a combobox', () => { const vNode = queryFixture( - `
-
` + html`
+
` ); assert.isFalse(isComboboxPopup(vNode)); }); @@ -52,7 +53,7 @@ describe('isComboboxPopup', () => { describe('using parent owned (ARIA 1.1 pattern)', () => { it('is true when its a child of the combobox', () => { const vNode = queryFixture( - `
+ html`
` ); @@ -61,7 +62,7 @@ describe('isComboboxPopup', () => { it('is false when its not a child of a real combobox', () => { const vNode = queryFixture( - `
+ html`
` ); @@ -70,7 +71,7 @@ describe('isComboboxPopup', () => { it('is false when its nearest parent with a role is not a combobox', () => { const vNode = queryFixture( - `
+ html`
@@ -81,7 +82,7 @@ describe('isComboboxPopup', () => { it('is true when its nearest parent with a role is not a combobox', () => { const vNode = queryFixture( - `
+ html`
@@ -98,24 +99,24 @@ describe('isComboboxPopup', () => { describe('when using aria-owns (ARIA 1.0 pattern)', () => { it('is true when referenced', () => { const vNode = queryFixture( - `
-
` + html`
+
` ); assert.isTrue(isComboboxPopup(vNode)); }); it('is false when owned by a select element', () => { const vNode = queryFixture( - ` -
` + html` +
` ); assert.isFalse(isComboboxPopup(vNode)); }); it('is false when not owned by a combobox', () => { const vNode = queryFixture( - `
-
` + html`
+
` ); assert.isFalse(isComboboxPopup(vNode)); }); @@ -126,8 +127,8 @@ describe('isComboboxPopup', () => { describe('options.popupRoles', () => { it('allows custom popup roles', () => { const vNode = queryFixture( - `
-
` + html`
+
` ); assert.isFalse(isComboboxPopup(vNode)); assert.isTrue(isComboboxPopup(vNode, { popupRoles: ['button'] })); @@ -135,8 +136,8 @@ describe('isComboboxPopup', () => { it('overrides the default popup roles', () => { const vNode = queryFixture( - `
-
` + html`
+
` ); assert.isTrue(isComboboxPopup(vNode)); assert.isFalse(isComboboxPopup(vNode, { popupRoles: ['button'] })); diff --git a/test/commons/aria/is-unsupported-role.js b/test/commons/aria/is-unsupported-role.js index 44e499f63..48ac2ca44 100644 --- a/test/commons/aria/is-unsupported-role.js +++ b/test/commons/aria/is-unsupported-role.js @@ -1,11 +1,9 @@ -describe('aria.isUnsupportedRole', function () { - 'use strict'; - - after(function () { +describe('aria.isUnsupportedRole', () => { + after(() => { axe.reset(); }); - it('should return true if the role is unsupported', function () { + it('should return true if the role is unsupported', () => { axe.configure({ standards: { ariaRoles: { @@ -18,7 +16,7 @@ describe('aria.isUnsupportedRole', function () { assert.isTrue(axe.commons.aria.isUnsupportedRole('cats')); }); - it('should return false if the role is supported', function () { + it('should return false if the role is supported', () => { axe.configure({ standards: { ariaRoles: { @@ -31,7 +29,7 @@ describe('aria.isUnsupportedRole', function () { assert.isFalse(axe.commons.aria.isUnsupportedRole('cats')); }); - it('should return false if the role is invalid', function () { + it('should return false if the role is invalid', () => { assert.isFalse(axe.commons.aria.isUnsupportedRole('cats')); }); }); diff --git a/test/commons/aria/is-valid-role.js b/test/commons/aria/is-valid-role.js index 3893e26d4..337f262de 100644 --- a/test/commons/aria/is-valid-role.js +++ b/test/commons/aria/is-valid-role.js @@ -1,11 +1,9 @@ -describe('aria.isValidRole', function () { - 'use strict'; - - afterEach(function () { +describe('aria.isValidRole', () => { + afterEach(() => { axe.reset(); }); - it('should return true if role is found in the lookup table', function () { + it('should return true if role is found in the lookup table', () => { axe.configure({ standards: { ariaRoles: { @@ -16,15 +14,15 @@ describe('aria.isValidRole', function () { assert.isTrue(axe.commons.aria.isValidRole('cats')); }); - it('should return false if role is not found in the lookup table', function () { + it('should return false if role is not found in the lookup table', () => { assert.isFalse(axe.commons.aria.isValidRole('cats')); }); - it('returns false for abstract roles by default', function () { + it('returns false for abstract roles by default', () => { assert.isFalse(axe.commons.aria.isValidRole('input')); }); - it('returns true for abstract roles with { allowAbstract: true } ', function () { + it('returns true for abstract roles with { allowAbstract: true } ', () => { assert.isTrue( axe.commons.aria.isValidRole('input', { allowAbstract: true }) ); diff --git a/test/commons/aria/label-virtual.js b/test/commons/aria/label-virtual.js index 4389bd3cd..6cec40f09 100644 --- a/test/commons/aria/label-virtual.js +++ b/test/commons/aria/label-virtual.js @@ -1,77 +1,86 @@ -describe('aria.labelVirtual', function () { - 'use strict'; +describe('aria.labelVirtual', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var fixtureSetup = axe.testUtils.fixtureSetup; + const fixture = document.getElementById('fixture'); + const fixtureSetup = axe.testUtils.fixtureSetup; - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; axe._tree = undefined; }); - it('is called through aria.label with a DOM node', function () { - fixtureSetup( - '
monkeys
bananas
' + - '' - ); - var target = fixture.querySelector('#target'); + it('is called through aria.label with a DOM node', () => { + fixtureSetup(html` +
monkeys
+
bananas
+ + `); + const target = fixture.querySelector('#target'); assert.equal(axe.commons.aria.label(target), 'monkeys bananas'); }); - describe('aria-labelledby', function () { - it('should join text with a single space', function () { - fixtureSetup( - '
monkeys
bananas
' + - '' - ); - var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + describe('aria-labelledby', () => { + it('should join text with a single space', () => { + fixtureSetup(html` +
monkeys
+
bananas
+ + `); + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; assert.equal(axe.commons.aria.labelVirtual(target), 'monkeys bananas'); }); - it('should filter invisible elements', function () { - fixtureSetup( - '
monkeys
' + - '' - ); - var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + it('should filter invisible elements', () => { + fixtureSetup(html` +
monkeys
+ + + `); + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; assert.equal(axe.commons.aria.labelVirtual(target), 'monkeys'); }); - it('should take precedence over aria-label', function () { - fixtureSetup( - '
monkeys
bananas
' + - '' - ); - var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + it('should take precedence over aria-label', () => { + fixtureSetup(html` +
monkeys
+
bananas
+ + `); + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; assert.equal(axe.commons.aria.labelVirtual(target), 'monkeys bananas'); }); - it('should ignore whitespace only labels', function () { - fixtureSetup( - '
\n
' + - '' - ); - var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + it('should ignore whitespace only labels', () => { + fixtureSetup(html` +
+
+ + `); + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; assert.isNull(axe.commons.aria.labelVirtual(target)); }); }); - describe('aria-label', function () { - it('should detect it', function () { + describe('aria-label', () => { + it('should detect it', () => { fixtureSetup(''); - var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; assert.equal(axe.commons.aria.labelVirtual(target), 'monkeys'); }); - it('should ignore whitespace only labels', function () { + it('should ignore whitespace only labels', () => { fixtureSetup(''); - var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + const target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; assert.isNull(axe.commons.aria.labelVirtual(target)); }); diff --git a/test/commons/aria/named-from-contents.js b/test/commons/aria/named-from-contents.js index d69ed9a8c..ec634f419 100644 --- a/test/commons/aria/named-from-contents.js +++ b/test/commons/aria/named-from-contents.js @@ -1,93 +1,93 @@ -describe('aria.namedFromContents', function () { - var aria = axe.commons.aria; - var namedFromContents = aria.namedFromContents; - var fixture = document.querySelector('#fixture'); - var flatTreeSetup = axe.testUtils.flatTreeSetup; +describe('aria.namedFromContents', () => { + const aria = axe.commons.aria; + const namedFromContents = aria.namedFromContents; + const fixture = document.querySelector('#fixture'); + const flatTreeSetup = axe.testUtils.flatTreeSetup; - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('returns true when the element has an explicit role named from content', function () { + it('returns true when the element has an explicit role named from content', () => { fixture.innerHTML = '
'; flatTreeSetup(fixture); assert.isTrue(namedFromContents(fixture.firstChild)); }); - it('works on virtual nodes', function () { - var vNode = axe.testUtils.queryFixture( + it('works on virtual nodes', () => { + const vNode = axe.testUtils.queryFixture( '
' ); assert.isTrue(namedFromContents(vNode)); }); - it('returns true when the element has an implicit role named from content', function () { + it('returns true when the element has an implicit role named from content', () => { fixture.innerHTML = '

foo

'; flatTreeSetup(fixture); assert.isTrue(namedFromContents(fixture.firstChild)); }); - it('returns false when the element has a role not named from content', function () { + it('returns false when the element has a role not named from content', () => { fixture.innerHTML = '
'; flatTreeSetup(fixture); assert.isFalse(namedFromContents(fixture.firstChild)); }); - it('returns false node is not a DOM element', function () { + it('returns false node is not a DOM element', () => { fixture.innerHTML = 'text node'; flatTreeSetup(fixture); assert.isFalse(namedFromContents(fixture.firstChild)); }); - describe('{ strict: false }', function () { - it('returns true when the element has no role named from content', function () { + describe('{ strict: false }', () => { + it('returns true when the element has no role named from content', () => { fixture.innerHTML = '
foo
'; flatTreeSetup(fixture); assert.isNull(aria.getRole(fixture.firstChild)); assert.isTrue(namedFromContents(fixture.firstChild, { strict: false })); }); - it('is default for aria.namedFromContents', function () { + it('is default for aria.namedFromContents', () => { fixture.innerHTML = '
foo
'; flatTreeSetup(fixture); assert.isNull(aria.getRole(fixture.firstChild)); assert.isTrue(namedFromContents(fixture.firstChild)); }); - it('returns true when the element has role=presentation', function () { + it('returns true when the element has role=presentation', () => { fixture.innerHTML = '
foo
'; flatTreeSetup(fixture); assert.isTrue(namedFromContents(fixture.firstChild, { strict: false })); }); - it('returns true when the element has role=none', function () { + it('returns true when the element has role=none', () => { fixture.innerHTML = '
foo
'; flatTreeSetup(fixture); assert.isTrue(namedFromContents(fixture.firstChild, { strict: false })); }); - it('returns true when the implicit role is null', function () { + it('returns true when the implicit role is null', () => { fixture.innerHTML = '
foo
'; flatTreeSetup(fixture); assert.isTrue(namedFromContents(fixture.firstChild, { strict: false })); }); }); - describe('{ strict: true }', function () { - it('returns false when the element has no role named from content', function () { + describe('{ strict: true }', () => { + it('returns false when the element has no role named from content', () => { fixture.innerHTML = '
foo
'; flatTreeSetup(fixture); assert.isNull(aria.getRole(fixture.firstChild)); assert.isFalse(namedFromContents(fixture.firstChild, { strict: true })); }); - it('returns false when the element has role=presentation', function () { + it('returns false when the element has role=presentation', () => { fixture.innerHTML = '
foo
'; flatTreeSetup(fixture); assert.isFalse(namedFromContents(fixture.firstChild, { strict: true })); }); - it('returns false when the element has role=none', function () { + it('returns false when the element has role=none', () => { fixture.innerHTML = '
foo
'; flatTreeSetup(fixture); assert.isFalse(namedFromContents(fixture.firstChild, { strict: true })); diff --git a/test/commons/aria/required-attr.js b/test/commons/aria/required-attr.js index 5c83ab566..cebee1462 100644 --- a/test/commons/aria/required-attr.js +++ b/test/commons/aria/required-attr.js @@ -1,11 +1,9 @@ -describe('aria.requiredAttr', function () { - 'use strict'; - - afterEach(function () { +describe('aria.requiredAttr', () => { + afterEach(() => { axe.reset(); }); - it('should returned the attributes property for the proper role', function () { + it('should returned the attributes property for the proper role', () => { axe.configure({ standards: { ariaRoles: { @@ -19,7 +17,7 @@ describe('aria.requiredAttr', function () { assert.deepEqual(axe.commons.aria.requiredAttr('cats'), ['yes']); }); - it('should returned empty array if the required attributes is not an array', function () { + it('should returned empty array if the required attributes is not an array', () => { axe.configure({ standards: { ariaRoles: { @@ -32,14 +30,14 @@ describe('aria.requiredAttr', function () { assert.deepEqual(axe.commons.aria.requiredAttr('cats'), []); }); - it('should return an empty array if there are no required attributes', function () { - var result = axe.commons.aria.requiredAttr('cats'); + it('should return an empty array if there are no required attributes', () => { + const result = axe.commons.aria.requiredAttr('cats'); assert.deepEqual(result, []); }); - it('should return a unique copy of the attributes', function () { - var attrs = ['yes', 'no']; + it('should return a unique copy of the attributes', () => { + const attrs = ['yes', 'no']; axe.configure({ standards: { @@ -51,7 +49,7 @@ describe('aria.requiredAttr', function () { } }); - var result = axe.commons.aria.requiredAttr('cats'); + const result = axe.commons.aria.requiredAttr('cats'); assert.notEqual(result, attrs); }); }); diff --git a/test/commons/aria/required-context.js b/test/commons/aria/required-context.js index 7608df5bc..efebab4c1 100644 --- a/test/commons/aria/required-context.js +++ b/test/commons/aria/required-context.js @@ -1,11 +1,9 @@ -describe('aria.requiredContext', function () { - 'use strict'; - - afterEach(function () { +describe('aria.requiredContext', () => { + afterEach(() => { axe.reset(); }); - it('should returned the context property for the proper role', function () { + it('should returned the context property for the proper role', () => { axe.configure({ standards: { ariaRoles: { @@ -18,7 +16,7 @@ describe('aria.requiredContext', function () { assert.deepEqual(axe.commons.aria.requiredContext('cats'), ['yes']); }); - it('should returned null if the required context is not an array', function () { + it('should returned null if the required context is not an array', () => { axe.configure({ standards: { ariaRoles: { @@ -31,14 +29,14 @@ describe('aria.requiredContext', function () { assert.isNull(axe.commons.aria.requiredContext('cats')); }); - it('should return null if there are no required context nodes', function () { - var result = axe.commons.aria.requiredContext('cats'); + it('should return null if there are no required context nodes', () => { + const result = axe.commons.aria.requiredContext('cats'); assert.isNull(result); }); - it('should return a unique copy of the context', function () { - var context = ['yes', 'no']; + it('should return a unique copy of the context', () => { + const context = ['yes', 'no']; axe.configure({ standards: { @@ -50,7 +48,7 @@ describe('aria.requiredContext', function () { } }); - var result = axe.commons.aria.requiredContext('cats'); + const result = axe.commons.aria.requiredContext('cats'); assert.notEqual(result, context); }); }); diff --git a/test/commons/aria/required-owned.js b/test/commons/aria/required-owned.js index 5f63e79da..9bc6bf517 100644 --- a/test/commons/aria/required-owned.js +++ b/test/commons/aria/required-owned.js @@ -1,11 +1,9 @@ -describe('aria.requiredOwned', function () { - 'use strict'; - - afterEach(function () { +describe('aria.requiredOwned', () => { + afterEach(() => { axe.reset(); }); - it('should returned the context property for the proper role', function () { + it('should returned the context property for the proper role', () => { axe.configure({ standards: { ariaRoles: { @@ -18,7 +16,7 @@ describe('aria.requiredOwned', function () { assert.deepEqual(axe.commons.aria.requiredOwned('cats'), ['yes']); }); - it('should returned null if the required context is not an array', function () { + it('should returned null if the required context is not an array', () => { axe.configure({ standards: { ariaRoles: { @@ -31,14 +29,14 @@ describe('aria.requiredOwned', function () { assert.isNull(axe.commons.aria.requiredOwned('cats')); }); - it('should return null if there are no required context nodes', function () { - var result = axe.commons.aria.requiredOwned('cats'); + it('should return null if there are no required context nodes', () => { + const result = axe.commons.aria.requiredOwned('cats'); assert.isNull(result); }); - it('should return a unique copy of the context', function () { - var context = ['yes', 'no']; + it('should return a unique copy of the context', () => { + const context = ['yes', 'no']; axe.configure({ standards: { @@ -50,7 +48,7 @@ describe('aria.requiredOwned', function () { } }); - var result = axe.commons.aria.requiredOwned('cats'); + const result = axe.commons.aria.requiredOwned('cats'); assert.notEqual(result, context); }); }); diff --git a/test/commons/aria/roles.js b/test/commons/aria/roles.js index 99a59867b..13cd83f68 100644 --- a/test/commons/aria/roles.js +++ b/test/commons/aria/roles.js @@ -1,16 +1,14 @@ -describe('aria.implicitNodes', function () { - 'use strict'; - - var orig; - beforeEach(function () { +describe('aria.implicitNodes', () => { + let orig; + beforeEach(() => { orig = axe.commons.aria.lookupTable.role; }); - afterEach(function () { + afterEach(() => { axe.commons.aria.lookupTable.role = orig; }); - it('should return the implicit property for the proper role', function () { + it('should return the implicit property for the proper role', () => { axe.commons.aria.lookupTable.role = { cats: { implicit: 'yes' @@ -19,9 +17,9 @@ describe('aria.implicitNodes', function () { assert.equal(axe.commons.aria.implicitNodes('cats'), 'yes'); }); - it('should return null if there are no implicit roles', function () { + it('should return null if there are no implicit roles', () => { axe.commons.aria.lookupTable.role = {}; - var result = axe.commons.aria.implicitNodes('cats'); + const result = axe.commons.aria.implicitNodes('cats'); assert.isNull(result); }); diff --git a/test/commons/aria/validate-attr-value.js b/test/commons/aria/validate-attr-value.js index c251fca15..9ab6dfab2 100644 --- a/test/commons/aria/validate-attr-value.js +++ b/test/commons/aria/validate-attr-value.js @@ -505,7 +505,6 @@ describe('aria.validateAttrValue', () => { }); function makeShadowTreeVAV(node) { - 'use strict'; const root = node.attachShadow({ mode: 'open' }); const div = document.createElement('div'); div.className = 'parent'; @@ -514,11 +513,11 @@ function makeShadowTreeVAV(node) { } function createContentVAV() { - 'use strict'; const group = document.createElement('div'); - group.innerHTML = - '' + - '' + - ''; + group.innerHTML = ` + + + + `; return group; } diff --git a/test/commons/aria/validate-attr.js b/test/commons/aria/validate-attr.js index fa25ce322..e5a65d0d4 100644 --- a/test/commons/aria/validate-attr.js +++ b/test/commons/aria/validate-attr.js @@ -1,11 +1,9 @@ -describe('aria.validateAttr', function () { - 'use strict'; - - afterEach(function () { +describe('aria.validateAttr', () => { + afterEach(() => { axe.reset(); }); - it('should return true if attribute is found in lut', function () { + it('should return true if attribute is found in lut', () => { axe.configure({ standards: { ariaAttrs: { @@ -17,7 +15,7 @@ describe('aria.validateAttr', function () { assert.isTrue(axe.commons.aria.validateAttr('cats')); }); - it('should return false if attribute is found in lut', function () { + it('should return false if attribute is found in lut', () => { assert.isFalse(axe.commons.aria.validateAttr('cats')); }); }); diff --git a/test/commons/color/center-point-of-rect.js b/test/commons/color/center-point-of-rect.js index 668594e04..0d3bd9c02 100644 --- a/test/commons/color/center-point-of-rect.js +++ b/test/commons/color/center-point-of-rect.js @@ -1,8 +1,6 @@ -describe('color.centerPointOfRect', function () { - 'use strict'; - - it('returns `undefined` when element is placed outside of viewport (left position > window dimension)', function () { - var actual = axe.commons.color.centerPointOfRect({ +describe('color.centerPointOfRect', () => { + it('returns `undefined` when element is placed outside of viewport (left position > window dimension)', () => { + const actual = axe.commons.color.centerPointOfRect({ left: 9999, top: 0, width: 200, @@ -11,8 +9,8 @@ describe('color.centerPointOfRect', function () { assert.isUndefined(actual); }); - it('returns `{x,y}` when element is with in viewport', function () { - var actual = axe.commons.color.centerPointOfRect({ + it('returns `{x,y}` when element is with in viewport', () => { + const actual = axe.commons.color.centerPointOfRect({ left: 0, top: 0, width: 200, @@ -22,8 +20,8 @@ describe('color.centerPointOfRect', function () { assert.hasAllKeys(actual, ['x', 'y']); }); - it('returns `{x,y}` when element is with in viewport (check returned coordinate values)', function () { - var actual = axe.commons.color.centerPointOfRect({ + it('returns `{x,y}` when element is with in viewport (check returned coordinate values)', () => { + const actual = axe.commons.color.centerPointOfRect({ left: 100, top: 100, width: 250, diff --git a/test/commons/color/color.js b/test/commons/color/color.js index 1c1230e7f..fb6a1fde9 100644 --- a/test/commons/color/color.js +++ b/test/commons/color/color.js @@ -1,5 +1,4 @@ describe('color.Color', () => { - 'use strict'; const Color = axe.commons.color.Color; it('can be constructed without alpha', () => { @@ -166,6 +165,15 @@ describe('color.Color', () => { assert.equal(c.alpha, 1); }); + it('supports negative rad on hue', () => { + const c = new Color(); + c.parseColorFnString('hsl(-3.49rad, 40%, 50%)'); + assert.equal(c.red, 77); + assert.equal(c.green, 179); + assert.equal(c.blue, 145); + assert.equal(c.alpha, 1); + }); + it('supports turn on hue', () => { const c = new Color(); c.parseColorFnString('hsl(0.444turn, 40%, 50%)'); @@ -174,6 +182,15 @@ describe('color.Color', () => { assert.equal(c.blue, 144); assert.equal(c.alpha, 1); }); + + it('supports negative turn on hue', () => { + const c = new Color(); + c.parseColorFnString('hsl(-0.556turn, 40%, 50%)'); + assert.equal(c.red, 77); + assert.equal(c.green, 179); + assert.equal(c.blue, 144); + assert.equal(c.alpha, 1); + }); }); describe('with hwb()', () => { @@ -283,6 +300,19 @@ describe('color.Color', () => { assert.equal(c.blue, 144); assert.equal(c.alpha, 0.5); }); + + it('clips out of gamut values', () => { + const c = new Color(); + c.parseColorFnString('oklch(25% 0.75 345)'); + assert.equal(c.red, 186); + assert.equal(c.green, 0); + assert.equal(c.blue, 103); + assert.equal(c.alpha, 1); + + assert.equal(c.red, Math.round(c.r * 255)); + assert.equal(c.green, Math.round(c.g * 255)); + assert.equal(c.blue, Math.round(c.b * 255)); + }); }); }); @@ -326,7 +356,7 @@ describe('color.Color', () => { it('does nothing when passed an invalid string', () => { const color = new Color(1, 2, 3, 0.4); const values = ['abcdef', '#abcde', '#XYZ', '#0123456789']; - values.forEach(function (val) { + values.forEach(val => { color.parseHexString(val); assert.equal(color.red, 1); assert.equal(color.green, 2); diff --git a/test/commons/color/element-has-image.js b/test/commons/color/element-has-image.js index 4b2c8e9fe..25f5b46fd 100644 --- a/test/commons/color/element-has-image.js +++ b/test/commons/color/element-has-image.js @@ -1,62 +1,53 @@ -describe('color.elementHasImage', function () { - 'use strict'; +describe('color.elementHasImage', () => { + const fixture = document.getElementById('fixture'); + const queryFixture = axe.testUtils.queryFixture; + const elementHasImage = axe.commons.color.elementHasImage; - var fixture = document.getElementById('fixture'); - var queryFixture = axe.testUtils.queryFixture; - var elementHasImage = axe.commons.color.elementHasImage; - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; axe._tree = undefined; axe.commons.color.incompleteData.clear(); }); - it('returns true when `HTMLElement` is of graphical type', function () { - ['img', 'canvas', 'object', 'iframe', 'video', 'svg'].forEach( - function (nodeName) { - var vNode = queryFixture( - '<' + nodeName + ' id="target">' - ); - var actual = elementHasImage(vNode.actualNode); - assert.isTrue(actual); - assert.equal( - axe.commons.color.incompleteData.get('bgColor'), - 'imgNode' - ); - } - ); + it('returns true when `HTMLElement` is of graphical type', () => { + ['img', 'canvas', 'object', 'iframe', 'video', 'svg'].forEach(nodeName => { + const vNode = queryFixture(`<${nodeName} id="target">`); + const actual = elementHasImage(vNode.actualNode); + assert.isTrue(actual); + assert.equal(axe.commons.color.incompleteData.get('bgColor'), 'imgNode'); + }); }); - it('returns false when `HTMLElement` has no background-image style set', function () { - var vNode = queryFixture( + it('returns false when `HTMLElement` has no background-image style set', () => { + const vNode = queryFixture( '
No background style
' ); - var actual = elementHasImage(vNode.actualNode); + const actual = elementHasImage(vNode.actualNode); assert.isFalse(actual); }); - it('returns false when `HTMLElement` has background-image style set to none', function () { - var vNode = queryFixture( + it('returns false when `HTMLElement` has background-image style set to none', () => { + const vNode = queryFixture( '
Some text...
' ); - var actual = elementHasImage(vNode.actualNode); + const actual = elementHasImage(vNode.actualNode); assert.isFalse(actual); }); - it('returns true when `HTMLElement` has background-image (url)', function () { - var vNode = queryFixture( + it('returns true when `HTMLElement` has background-image (url)', () => { + const vNode = queryFixture( '
Some text...
' ); - var actual = elementHasImage(vNode.actualNode); + const actual = elementHasImage(vNode.actualNode); assert.isTrue(actual); assert.equal(axe.commons.color.incompleteData.get('bgColor'), 'bgImage'); }); - it('returns true when `HTMLElement` has background-image (gradient)', function () { - var vNode = queryFixture( + it('returns true when `HTMLElement` has background-image (gradient)', () => { + const vNode = queryFixture( '
Some text...
' ); - var actual = elementHasImage(vNode.actualNode); + const actual = elementHasImage(vNode.actualNode); assert.isTrue(actual); assert.equal(axe.commons.color.incompleteData.get('bgColor'), 'bgGradient'); }); diff --git a/test/commons/color/element-is-distinct.js b/test/commons/color/element-is-distinct.js index 413482bb3..54a016dda 100644 --- a/test/commons/color/element-is-distinct.js +++ b/test/commons/color/element-is-distinct.js @@ -38,35 +38,33 @@ describe('color.elementIsDistinct', () => { if (!name) { return propPiece; } else { - return name + '-' + propPiece.toLowerCase(); + return `${name}-${propPiece.toLowerCase()}`; } }, null); // Return indented line of style code - return ' ' + cssPropName + ':' + styleObj[prop] + ';'; + return ` ${cssPropName}:${styleObj[prop]};`; }) .join('\n'); // Add to the style element - styleElm.innerHTML += selector + ' {\n' + cssLines + '\n}\n'; + styleElm.innerHTML += `${selector} { +${cssLines} +} +`; } function getLinkElm(linkStyle, paragraphStyle) { // Get a random id and build the style string - const linkId = 'linkid-' + Math.floor(Math.random() * 100000); - const parId = 'parid-' + Math.floor(Math.random() * 100000); - - createStyleString('#' + linkId, linkStyle); - createStyleString('#' + parId, paragraphStyle); - - fixture.innerHTML += - '

Text ' + - 'link' + - '

'; + const linkId = `linkid-${Math.floor(Math.random() * 100000)}`; + const parId = `parid-${Math.floor(Math.random() * 100000)}`; + + createStyleString(`#${linkId}`, linkStyle); + createStyleString(`#${parId}`, paragraphStyle); + + fixture.innerHTML += `

Text + link +

`; return { link: document.getElementById(linkId), par: document.getElementById(parId) diff --git a/test/commons/color/flatten-colors.js b/test/commons/color/flatten-colors.js index 0d152616b..0f0418ece 100644 --- a/test/commons/color/flatten-colors.js +++ b/test/commons/color/flatten-colors.js @@ -1,7 +1,7 @@ -describe('color.flattenColors', function () { +describe('color.flattenColors', () => { const { Color, flattenColors } = axe.commons.color; - it('should flatten colors properly', function () { + it('should flatten colors properly', () => { const halfBlack = new Color(0, 0, 0, 0.5); const fullBlack = new Color(0, 0, 0, 1); const transparent = new Color(0, 0, 0, 0); @@ -57,17 +57,15 @@ describe('color.flattenColors', function () { }); }); -describe('color.flattenColors ', function () { - 'use strict'; +describe('color.flattenColors ', () => { + const colourOne = new axe.commons.color.Color(216, 22, 22, 1); + const colourTwo = new axe.commons.color.Color(114, 129, 114, 0.25); - var colourOne = new axe.commons.color.Color(216, 22, 22, 1); - var colourTwo = new axe.commons.color.Color(114, 129, 114, 0.25); + const colourThree = new axe.commons.color.Color(211, 162, 180, 1); + const colourFour = new axe.commons.color.Color(115, 255, 0, 0.5); - var colourThree = new axe.commons.color.Color(211, 162, 180, 1); - var colourFour = new axe.commons.color.Color(115, 255, 0, 0.5); - - it('should flatten colors correctly using blend mode: multiply', function () { - var flatten = axe.commons.color.flattenColors( + it('should flatten colors correctly using blend mode: multiply', () => { + const flatten = axe.commons.color.flattenColors( colourTwo, colourOne, 'multiply' @@ -77,7 +75,7 @@ describe('color.flattenColors ', function () { assert.equal(flatten.blue, 19); assert.equal(flatten.alpha, 1); - var flattenTwo = axe.commons.color.flattenColors( + const flattenTwo = axe.commons.color.flattenColors( colourFour, colourThree, 'multiply' @@ -88,8 +86,8 @@ describe('color.flattenColors ', function () { assert.equal(flattenTwo.alpha, 1); }); - it('should flatten colors correctly using blend mode: screen', function () { - var flatten = axe.commons.color.flattenColors( + it('should flatten colors correctly using blend mode: screen', () => { + const flatten = axe.commons.color.flattenColors( colourTwo, colourOne, 'screen' @@ -99,7 +97,7 @@ describe('color.flattenColors ', function () { assert.equal(flatten.blue, 48); assert.equal(flatten.alpha, 1); - var flattenTwo = axe.commons.color.flattenColors( + const flattenTwo = axe.commons.color.flattenColors( colourFour, colourThree, 'screen' @@ -110,8 +108,8 @@ describe('color.flattenColors ', function () { assert.equal(flattenTwo.alpha, 1); }); - it('should flatten colors correctly using blend mode: overlay', function () { - var flatten = axe.commons.color.flattenColors( + it('should flatten colors correctly using blend mode: overlay', () => { + const flatten = axe.commons.color.flattenColors( colourTwo, colourOne, 'overlay' @@ -122,8 +120,8 @@ describe('color.flattenColors ', function () { assert.equal(flatten.alpha, 1); }); - it('should flatten colors correctly using blend mode: darken', function () { - var flatten = axe.commons.color.flattenColors( + it('should flatten colors correctly using blend mode: darken', () => { + const flatten = axe.commons.color.flattenColors( colourTwo, colourOne, 'darken' @@ -133,7 +131,7 @@ describe('color.flattenColors ', function () { assert.equal(flatten.blue, 22); assert.equal(flatten.alpha, 1); - var flattenTwo = axe.commons.color.flattenColors( + const flattenTwo = axe.commons.color.flattenColors( colourFour, colourThree, 'darken' @@ -144,8 +142,8 @@ describe('color.flattenColors ', function () { assert.equal(flattenTwo.alpha, 1); }); - it('should flatten colors correctly using blend mode: lighten', function () { - var flatten = axe.commons.color.flattenColors( + it('should flatten colors correctly using blend mode: lighten', () => { + const flatten = axe.commons.color.flattenColors( colourTwo, colourOne, 'lighten' @@ -155,7 +153,7 @@ describe('color.flattenColors ', function () { assert.equal(flatten.blue, 45); assert.equal(flatten.alpha, 1); - var flattenTwo = axe.commons.color.flattenColors( + const flattenTwo = axe.commons.color.flattenColors( colourFour, colourThree, 'lighten' @@ -166,8 +164,8 @@ describe('color.flattenColors ', function () { assert.equal(flattenTwo.alpha, 1); }); - it('should flatten colors correctly using blend mode: color-dodge', function () { - var flatten = axe.commons.color.flattenColors( + it('should flatten colors correctly using blend mode: color-dodge', () => { + const flatten = axe.commons.color.flattenColors( colourTwo, colourOne, 'color-dodge' @@ -177,7 +175,7 @@ describe('color.flattenColors ', function () { assert.equal(flatten.blue, 26); assert.equal(flatten.alpha, 1); - var flattenTwo = axe.commons.color.flattenColors( + const flattenTwo = axe.commons.color.flattenColors( colourFour, colourThree, 'color-dodge' @@ -188,8 +186,8 @@ describe('color.flattenColors ', function () { assert.equal(flattenTwo.alpha, 1); }); - it('should flatten colors correctly using blend mode: color-burn', function () { - var flatten = axe.commons.color.flattenColors( + it('should flatten colors correctly using blend mode: color-burn', () => { + const flatten = axe.commons.color.flattenColors( colourTwo, colourOne, 'color-burn' @@ -199,7 +197,7 @@ describe('color.flattenColors ', function () { assert.equal(flatten.blue, 17); assert.equal(flatten.alpha, 1); - var flattenTwo = axe.commons.color.flattenColors( + const flattenTwo = axe.commons.color.flattenColors( colourFour, colourThree, 'color-burn' @@ -210,8 +208,8 @@ describe('color.flattenColors ', function () { assert.equal(flattenTwo.alpha, 1); }); - it('should flatten colors correctly using blend mode: hard-light', function () { - var flatten = axe.commons.color.flattenColors( + it('should flatten colors correctly using blend mode: hard-light', () => { + const flatten = axe.commons.color.flattenColors( colourTwo, colourOne, 'hard-light' @@ -221,7 +219,7 @@ describe('color.flattenColors ', function () { assert.equal(flatten.blue, 21); assert.equal(flatten.alpha, 1); - var flattenTwo = axe.commons.color.flattenColors( + const flattenTwo = axe.commons.color.flattenColors( colourFour, colourThree, 'hard-light' @@ -232,8 +230,8 @@ describe('color.flattenColors ', function () { assert.equal(flattenTwo.alpha, 1); }); - it('should flatten colors correctly using blend mode: soft-light', function () { - var flatten = axe.commons.color.flattenColors( + it('should flatten colors correctly using blend mode: soft-light', () => { + const flatten = axe.commons.color.flattenColors( colourTwo, colourOne, 'soft-light' @@ -243,7 +241,7 @@ describe('color.flattenColors ', function () { assert.equal(flatten.blue, 21); assert.equal(flatten.alpha, 1); - var flattenTwo = axe.commons.color.flattenColors( + const flattenTwo = axe.commons.color.flattenColors( colourFour, colourThree, 'soft-light' @@ -254,8 +252,8 @@ describe('color.flattenColors ', function () { assert.equal(flattenTwo.alpha, 1); }); - it('should flatten colors correctly using blend mode: difference', function () { - var flatten = axe.commons.color.flattenColors( + it('should flatten colors correctly using blend mode: difference', () => { + const flatten = axe.commons.color.flattenColors( colourTwo, colourOne, 'difference' @@ -265,7 +263,7 @@ describe('color.flattenColors ', function () { assert.equal(flatten.blue, 40); assert.equal(flatten.alpha, 1); - var flattenTwo = axe.commons.color.flattenColors( + const flattenTwo = axe.commons.color.flattenColors( colourFour, colourThree, 'difference' @@ -276,8 +274,8 @@ describe('color.flattenColors ', function () { assert.equal(flattenTwo.alpha, 1); }); - it('should flatten colors correctly using blend mode: exclusion', function () { - var flatten = axe.commons.color.flattenColors( + it('should flatten colors correctly using blend mode: exclusion', () => { + const flatten = axe.commons.color.flattenColors( colourTwo, colourOne, 'exclusion' @@ -287,7 +285,7 @@ describe('color.flattenColors ', function () { assert.equal(flatten.blue, 46); assert.equal(flatten.alpha, 1); - var flattenTwo = axe.commons.color.flattenColors( + const flattenTwo = axe.commons.color.flattenColors( colourFour, colourThree, 'exclusion' @@ -298,14 +296,18 @@ describe('color.flattenColors ', function () { assert.equal(flattenTwo.alpha, 1); }); - it('should flatten colors correctly using blend mode: hue', function () { - var flatten = axe.commons.color.flattenColors(colourTwo, colourOne, 'hue'); + it('should flatten colors correctly using blend mode: hue', () => { + const flatten = axe.commons.color.flattenColors( + colourTwo, + colourOne, + 'hue' + ); assert.equal(flatten.red, 162); assert.equal(flatten.green, 50); assert.equal(flatten.blue, 17); assert.equal(flatten.alpha, 1); - var flattenTwo = axe.commons.color.flattenColors( + const flattenTwo = axe.commons.color.flattenColors( colourFour, colourThree, 'hue' @@ -316,8 +318,8 @@ describe('color.flattenColors ', function () { assert.equal(flattenTwo.alpha, 1); }); - it('should flatten colors correctly using blend mode: saturation', function () { - var flatten = axe.commons.color.flattenColors( + it('should flatten colors correctly using blend mode: saturation', () => { + const flatten = axe.commons.color.flattenColors( colourTwo, colourOne, 'saturation' @@ -327,7 +329,7 @@ describe('color.flattenColors ', function () { assert.equal(flatten.blue, 35); assert.equal(flatten.alpha, 1); - var flattenTwo = axe.commons.color.flattenColors( + const flattenTwo = axe.commons.color.flattenColors( colourFour, colourThree, 'saturation' @@ -338,8 +340,8 @@ describe('color.flattenColors ', function () { assert.equal(flattenTwo.alpha, 1); }); - it('should flatten colors correctly using blend mode: color', function () { - var flatten = axe.commons.color.flattenColors( + it('should flatten colors correctly using blend mode: color', () => { + const flatten = axe.commons.color.flattenColors( colourTwo, colourOne, 'color' @@ -349,7 +351,7 @@ describe('color.flattenColors ', function () { assert.equal(flatten.blue, 34); assert.equal(flatten.alpha, 1); - var flattenTwo = axe.commons.color.flattenColors( + const flattenTwo = axe.commons.color.flattenColors( colourFour, colourThree, 'color' @@ -360,8 +362,8 @@ describe('color.flattenColors ', function () { assert.equal(flattenTwo.alpha, 1); }); - it('should flatten colors correctly using blend mode: luminosity', function () { - var flatten = axe.commons.color.flattenColors( + it('should flatten colors correctly using blend mode: luminosity', () => { + const flatten = axe.commons.color.flattenColors( colourTwo, colourOne, 'luminosity' @@ -371,7 +373,7 @@ describe('color.flattenColors ', function () { assert.equal(flatten.blue, 33); assert.equal(flatten.alpha, 1); - var flattenTwo = axe.commons.color.flattenColors( + const flattenTwo = axe.commons.color.flattenColors( colourFour, colourThree, 'luminosity' diff --git a/test/commons/color/get-background-color.js b/test/commons/color/get-background-color.js index 12852c69d..c6e26babd 100644 --- a/test/commons/color/get-background-color.js +++ b/test/commons/color/get-background-color.js @@ -1,9 +1,9 @@ -describe('color.getBackgroundColor', function () { - 'use strict'; +describe('color.getBackgroundColor', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); + const fixture = document.getElementById('fixture'); - var shadowSupported = axe.testUtils.shadowSupport.v1; + const shadowSupported = axe.testUtils.shadowSupport.v1; /** * Assert that two Colors are close-to-equal. @@ -24,196 +24,281 @@ describe('color.getBackgroundColor', function () { assert.closeTo(actual.alpha, expected.alpha, alphaThreshold, 'alpha'); } - beforeEach(function () { + beforeEach(() => { // This normalizes the default mocha behavior of setting a different background // based on prefers-color-scheme settings. document.body.style.background = '#fff'; document.documentElement.style.background = 'unset'; }); - afterEach(function () { + afterEach(() => { axe.commons.color.incompleteData.clear(); axe._tree = undefined; }); - it('should return the blended color if it has no background set', function () { - fixture.innerHTML = - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var parent = fixture.querySelector('#parent'); - var bgNodes = []; + it('should return the blended color if it has no background set', () => { + fixture.innerHTML = html` +
+
+
+ `; + const target = fixture.querySelector('#target'); + const parent = fixture.querySelector('#parent'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(128, 0, 0, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(128, 0, 0, 1); assertColorsClose(actual, expected); assert.deepEqual(bgNodes, [parent]); }); - it('should return the blended color if it is transparent and positioned', function () { - fixture.innerHTML = - '
' + - '
' + - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var pos = fixture.querySelector('#pos'); - var bgNodes = []; + it('should return the blended color if it is transparent and positioned', () => { + fixture.innerHTML = html` +
+
+
+
+
+
+ `; + const target = fixture.querySelector('#target'); + const pos = fixture.querySelector('#pos'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(64, 64, 0, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(64, 64, 0, 1); assertColorsClose(actual, expected); assert.deepEqual(bgNodes, [target, pos]); }); - it('should do alpha blending from the back forward', function () { - fixture.innerHTML = - '
' + - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var under = fixture.querySelector('#under'); - var bgNodes = []; + it('should do alpha blending from the back forward', () => { + fixture.innerHTML = html` +
+
+
+
+
+ `; + const target = fixture.querySelector('#target'); + const under = fixture.querySelector('#under'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(64, 64, 0, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(64, 64, 0, 1); assertColorsClose(actual, expected); assert.deepEqual(bgNodes, [target, under]); }); - it('should only look at what is underneath original element when blended and positioned', function () { - fixture.innerHTML = - '
' + - '
' + - '
' + - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var under = fixture.querySelector('#under'); - var bgNodes = []; + it('should only look at what is underneath original element when blended and positioned', () => { + fixture.innerHTML = html` +
+
+
+
+
+
+
+ `; + const target = fixture.querySelector('#target'); + const under = fixture.querySelector('#under'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(64, 64, 0, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(64, 64, 0, 1); assertColorsClose(actual, expected); assert.deepEqual(bgNodes, [target, under]); }); - it('should return the proper blended color if it has alpha set', function () { - fixture.innerHTML = - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var parent = fixture.querySelector('#parent'); - var bgNodes = []; + it('should return the proper blended color if it has alpha set', () => { + fixture.innerHTML = html` +
+
+
+ `; + const target = fixture.querySelector('#target'); + const parent = fixture.querySelector('#parent'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(64, 64, 0, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(64, 64, 0, 1); assertColorsClose(actual, expected); assert.deepEqual(bgNodes, [target, parent]); }); - it('should return the blended color if it has opacity set', function () { - fixture.innerHTML = - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var parent = fixture.querySelector('#parent'); - var bgNodes = []; + it('should return the blended color if it has opacity set', () => { + fixture.innerHTML = html` +
+
+
+ `; + const target = fixture.querySelector('#target'); + const parent = fixture.querySelector('#parent'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(64, 64, 0, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(64, 64, 0, 1); assert.deepEqual(actual, expected); assert.deepEqual(bgNodes, [target, parent]); }); - it('should apply opacity after blending', function () { - fixture.innerHTML = ` -
-
-
`; - var target = fixture.querySelector('#target'); - var bgNodes = []; + it('should apply opacity after blending', () => { + fixture.innerHTML = html`
+
+
`; + const target = fixture.querySelector('#target'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(102, 153, 51, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(102, 153, 51, 1); assert.deepEqual(actual, expected); }); - it('should apply opacity from an ancestor not in the element stack', function () { - fixture.innerHTML = ` -
-
-
-
-
`; - var target = fixture.querySelector('#target'); - var bgNodes = []; + it('should apply opacity from an ancestor not in the element stack', () => { + fixture.innerHTML = html`
+
+
+
+
`; + const target = fixture.querySelector('#target'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(102, 153, 51, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(102, 153, 51, 1); assert.deepEqual(actual, expected); }); - it('should return null if containing parent has a background image and is non-opaque', function () { - fixture.innerHTML = - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var parent = fixture.querySelector('#parent'); - var bgNodes = []; + it('should return null if containing parent has a background image and is non-opaque', () => { + fixture.innerHTML = html` +
+
+
+ `; + const target = fixture.querySelector('#target'); + const parent = fixture.querySelector('#parent'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); assert.isNull(actual); assert.deepEqual(bgNodes, [target, parent]); assert.equal(axe.commons.color.incompleteData.get('bgColor'), 'bgImage'); }); - it('should return body color if transparency goes all the way up to document', function () { + it('should return body color if transparency goes all the way up to document', () => { fixture.innerHTML = '
'; - var target = fixture.querySelector('#target'); + const target = fixture.querySelector('#target'); axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target); - var expected = new axe.commons.color.Color(255, 255, 255, 1); + const actual = axe.commons.color.getBackgroundColor(target); + const expected = new axe.commons.color.Color(255, 255, 255, 1); assert.deepEqual(actual, expected); }); - it('should return null if there is a background image', function () { - fixture.innerHTML = - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var bgNodes = []; + it('should return null if there is a background image', () => { + fixture.innerHTML = html` +
+
+
+ `; + const target = fixture.querySelector('#target'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); assert.isNull(actual); assert.deepEqual(bgNodes, [target]); assert.equal(axe.commons.color.incompleteData.get('bgColor'), 'bgImage'); }); - it('should return null if something non-opaque is obscuring it', function () { - fixture.innerHTML = - '
' + - '
Hello
'; + it('should return null if something non-opaque is obscuring it', () => { + fixture.innerHTML = html` +
+
+ Hello +
+ `; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target'), [] ); @@ -221,27 +306,38 @@ describe('color.getBackgroundColor', function () { assert.isNull(actual); }); - it('should return null if something non-opaque is obscuring it, scrolled out of view', function () { - fixture.innerHTML = - '
' + - '
' + - '
foo
' + - '
'; + it('should return null if something non-opaque is obscuring it, scrolled out of view', () => { + fixture.innerHTML = html` +
+
+
foo
+
+ `; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target') ); assert.equal(axe.commons.color.incompleteData.get('bgColor'), 'bgOverlap'); assert.isNull(actual); }); - it('should return an actual if something opaque is obscuring it', function () { - fixture.innerHTML = - '
' + - '
Hello
'; + it('should return an actual if something opaque is obscuring it', () => { + fixture.innerHTML = html` +
+
+ Hello +
+ `; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target'), [] ); @@ -249,28 +345,37 @@ describe('color.getBackgroundColor', function () { assert.isNull(actual); }); - it('should return the bgcolor if it is solid', function () { - fixture.innerHTML = - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var bgNodes = []; + it('should return the bgcolor if it is solid', () => { + fixture.innerHTML = html` +
+
+
+ `; + const target = fixture.querySelector('#target'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(0, 128, 0, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(0, 128, 0, 1); assert.deepEqual(actual, expected); assert.deepEqual(bgNodes, [target]); }); - it('should return a bgcolor for a multiline inline element fully covering the background', function () { - fixture.innerHTML = - '
' + - '
' + - '

Text oh heyyyy and here\'s
a link

' + - '
'; + it('should return a bgcolor for a multiline inline element fully covering the background', () => { + fixture.innerHTML = html` +
+
+

+ Text oh heyyyy and here's
a link
+

+
+ `; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target'), [] ); @@ -280,14 +385,19 @@ describe('color.getBackgroundColor', function () { assert.equal(Math.round(actual.green), 0); }); - it('should return null if a multiline inline element does not fully cover background', function () { - fixture.innerHTML = - '
' + - '
' + - '

Text oh heyyyy and here\'s
a link

' + - '
'; + it('should return null if a multiline inline element does not fully cover background', () => { + fixture.innerHTML = html` +
+
+

+ Text oh heyyyy and here's
a link
+

+
+ `; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target'), [] ); @@ -298,28 +408,37 @@ describe('color.getBackgroundColor', function () { ); }); - it('should return an actual if an absolutely positioned element does not cover background', function () { - fixture.innerHTML = - '
' + - '
Text
' + - '
'; + it('should return an actual if an absolutely positioned element does not cover background', () => { + fixture.innerHTML = html` +
+
+ Text +
+
+ `; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target'), [] ); - var expected = new axe.commons.color.Color(255, 255, 255); + const expected = new axe.commons.color.Color(255, 255, 255); assertColorsClose(actual, expected); }); - it('should return null if an absolutely positioned element partially obsures background', function () { - fixture.innerHTML = - '
' + - '
' + - '
Text
' + - '
'; + it('should return null if an absolutely positioned element partially obsures background', () => { + fixture.innerHTML = html` +
+
+
+ Text +
+
+ `; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target'), [] ); @@ -330,364 +449,460 @@ describe('color.getBackgroundColor', function () { ); }); - it('should count a TR as a background element for TD', function () { - fixture.innerHTML = - '
' + - '' + - '' + - '' + - '' + - '
' + - 'Cell content
'; - var target = fixture.querySelector('#target'), + it('should count a TR as a background element for TD', () => { + fixture.innerHTML = html` +
+ + + + +
Cell content
+
+ `; + const target = fixture.querySelector('#target'), parent = fixture.querySelector('#parent'); - var bgNodes = []; + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(243, 243, 243, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(243, 243, 243, 1); assert.deepEqual(actual, expected); assert.deepEqual(bgNodes, [parent]); }); - it('should count a TR as a background element for TH', function () { - fixture.innerHTML = - '
' + - '' + - '' + - '' + - '' + - '
' + - 'Header content
'; - var target = fixture.querySelector('#target'), + it('should count a TR as a background element for TH', () => { + fixture.innerHTML = html` +
+ + + + +
Header content
+
+ `; + const target = fixture.querySelector('#target'), parent = fixture.querySelector('#parent'); - var bgNodes = []; + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(243, 243, 243, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(243, 243, 243, 1); assert.deepEqual(actual, expected); assert.deepEqual(bgNodes, [parent]); }); - it('should count a TR as a background element for a child element', function () { - fixture.innerHTML = - '
' + - '' + - '' + - '' + - '
' + - 'Cell content' + - '
'; - var target = fixture.querySelector('#target'), + it('should count a TR as a background element for a child element', () => { + fixture.innerHTML = html` +
+ + + + +
+ Cell content +
+
+ `; + const target = fixture.querySelector('#target'), parent = fixture.querySelector('#parent'); - var bgNodes = []; + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(243, 243, 243, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(243, 243, 243, 1); assert.deepEqual(actual, expected); assert.deepEqual(bgNodes, [parent]); }); - it('should count a THEAD as a background element for a child element', function () { - fixture.innerHTML = - '
' + - '' + - '' + - '' + - '
' + - 'Cell content' + - '
'; - var target = fixture.querySelector('#target'), + it('should count a THEAD as a background element for a child element', () => { + fixture.innerHTML = html` +
+ + + + +
+ Cell content +
+
+ `; + const target = fixture.querySelector('#target'), parent = fixture.querySelector('#parent'); - var bgNodes = []; + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(243, 243, 243, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(243, 243, 243, 1); assert.deepEqual(actual, expected); assert.deepEqual(bgNodes, [parent]); }); - it('should count a TBODY as a background element for a child element', function () { - fixture.innerHTML = - '
' + - '' + - '' + - '' + - '
' + - 'Cell content' + - '
'; - var target = fixture.querySelector('#target'), + it('should count a TBODY as a background element for a child element', () => { + fixture.innerHTML = html` +
+ + + + +
+ Cell content +
+
+ `; + const target = fixture.querySelector('#target'), parent = fixture.querySelector('#parent'); - var bgNodes = []; + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(243, 243, 243, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(243, 243, 243, 1); assert.deepEqual(actual, expected); assert.deepEqual(bgNodes, [parent]); }); - it('should count a TFOOT as a background element for a child element', function () { - fixture.innerHTML = - '
' + - '' + - '' + - '' + - '
' + - 'Cell content' + - '
'; - var target = fixture.querySelector('#target'), + it('should count a TFOOT as a background element for a child element', () => { + fixture.innerHTML = html` +
+ + + + +
+ Cell content +
+
+ `; + const target = fixture.querySelector('#target'), parent = fixture.querySelector('#parent'); - var bgNodes = []; + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(243, 243, 243, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(243, 243, 243, 1); assert.deepEqual(actual, expected); assert.deepEqual(bgNodes, [parent]); }); - it("should ignore TR elements that don't overlap", function () { - fixture.innerHTML = - '' + - '' + - '' + - '
Content
'; - var bgNodes = []; - var target = fixture.querySelector('#target'); - var parent = fixture.querySelector('#parent'); + it("should ignore TR elements that don't overlap", () => { + fixture.innerHTML = html` + + + + +
Content
+ `; + const bgNodes = []; + const target = fixture.querySelector('#target'); + const parent = fixture.querySelector('#parent'); axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(255, 255, 255, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(255, 255, 255, 1); assert.deepEqual(actual, expected); assert.notEqual(bgNodes, [parent]); }); - it('should count an implicit label as a background element', function () { - fixture.innerHTML = - ''; - var target = fixture.querySelector('#target'); - var bgNodes = []; + it('should count an implicit label as a background element', () => { + fixture.innerHTML = html` + + `; + const target = fixture.querySelector('#target'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(0, 0, 0, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(0, 0, 0, 1); assert.deepEqual(actual, expected); }); - it('handles nested inline elements in the middle of a text', function () { - fixture.innerHTML = - '
' + - '
' + - ' Text ' + - ' ' + - ' ' + - '
'; - - var target = fixture.querySelector('#target'); - var bgNodes = []; + it('handles nested inline elements in the middle of a text', () => { + fixture.innerHTML = html` +
+
+ Text + + +
+ `; + + const target = fixture.querySelector('#target'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(0, 255, 255, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(0, 255, 255, 1); assert.deepEqual(actual, expected); }); - it('should return null for inline elements with position:absolute', function () { - fixture.innerHTML = - '
' + - '
' + - ' ' + - ' Text' + - ' ' + - '
'; + it('should return null for inline elements with position:absolute', () => { + fixture.innerHTML = html` +
+
+ + Text + +
+ `; axe.testUtils.flatTreeSetup(fixture); - var target = fixture.querySelector('#target'); - var actual = axe.commons.color.getBackgroundColor(target); + const target = fixture.querySelector('#target'); + const actual = axe.commons.color.getBackgroundColor(target); assert.equal(axe.commons.color.incompleteData.get('bgColor'), 'bgOverlap'); assert.isNull(actual); }); - it('should ignore inline ancestors of non-overlapping elements', function () { - fixture.innerHTML = - '
' + - '
'; - var target = fixture.querySelector('#target'); - var parent = fixture.querySelector('#parent'); - var bgNodes = []; + it('should ignore inline ancestors of non-overlapping elements', () => { + fixture.innerHTML = html` +
+ +
+ `; + const target = fixture.querySelector('#target'); + const parent = fixture.querySelector('#parent'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(255, 255, 255, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(255, 255, 255, 1); assert.deepEqual(actual, expected); assert.notEqual(bgNodes, [parent]); }); - it('should handle multiple ancestors of the same name', function () { - fixture.innerHTML = - '
' + - '' + - '' + - '
' + - '' + - '' + - '' + - '' + - '
' + - 'Cell content
' + - '
'; - var target = fixture.querySelector('#target'), + it('should handle multiple ancestors of the same name', () => { + fixture.innerHTML = html` +
+ + + + +
+ + + + +
+ Cell content +
+
+
+ `; + const target = fixture.querySelector('#target'), parent = fixture.querySelector('#parent'); - var bgNodes = []; + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(243, 243, 243, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(243, 243, 243, 1); assert.deepEqual(actual, expected); assert.deepEqual(bgNodes, [parent]); }); - it('should use hierarchical DOM traversal if possible', function () { - fixture.innerHTML = - '
' + - '
' + - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var parent = fixture.querySelector('#parent'); - var bgNodes = []; + it('should use hierarchical DOM traversal if possible', () => { + fixture.innerHTML = html` +
+
+
+
+ `; + const target = fixture.querySelector('#target'); + const parent = fixture.querySelector('#parent'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(255, 255, 255, 1); + const expected = new axe.commons.color.Color(255, 255, 255, 1); assertColorsClose(actual, expected); assert.deepEqual(bgNodes, [parent]); }); - it('should ignore 0-height elements', function () { - fixture.innerHTML = - '
' + - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var parent = fixture.querySelector('#parent'); - var bgNodes = []; + it('should ignore 0-height elements', () => { + fixture.innerHTML = html` +
+
+
+
+
+ `; + const target = fixture.querySelector('#target'); + const parent = fixture.querySelector('#parent'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(255, 255, 255, 1); + const expected = new axe.commons.color.Color(255, 255, 255, 1); assertColorsClose(actual, expected); assert.deepEqual(bgNodes, [parent]); }); - it('should use visual traversal when needed', function () { - fixture.innerHTML = - '
' + - '
' + - '
' + - '
'; - - var target = fixture.querySelector('#target'); - var shifted = fixture.querySelector('#shifted'); - var bgNodes = []; + it('should use visual traversal when needed', () => { + fixture.innerHTML = html` +
+
+
+
+ `; + + const target = fixture.querySelector('#target'); + const shifted = fixture.querySelector('#shifted'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes, false); - var expected = new axe.commons.color.Color(0, 0, 0, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes, false); + const expected = new axe.commons.color.Color(0, 0, 0, 1); assert.deepEqual(bgNodes, [shifted]); assertColorsClose(actual, expected); }); - it('should return null when encountering background images during visual traversal', function () { - fixture.innerHTML = - '
' + - '
' + - '
' + - '
' + - '
'; - - var target = fixture.querySelector('#target'); - var bgNodes = []; + it('should return null when encountering background images during visual traversal', () => { + fixture.innerHTML = html` +
+
+
+
+ `; + + const target = fixture.querySelector('#target'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var outcome = axe.commons.color.getBackgroundColor(target, bgNodes, false); + const outcome = axe.commons.color.getBackgroundColor( + target, + bgNodes, + false + ); assert.isNull(outcome); assert.equal(axe.commons.color.incompleteData.get('bgColor'), 'bgImage'); }); - it('should return null when encountering image nodes during visual traversal', function () { - fixture.innerHTML = - '
' + - '
' + - ' ' + - '
' + - '
' + - '
'; - - var target = fixture.querySelector('#target'); - var bgNodes = []; + it('should return null when encountering image nodes during visual traversal', () => { + fixture.innerHTML = html` +
+
+ +
+
+
+ `; + + const target = fixture.querySelector('#target'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var outcome = axe.commons.color.getBackgroundColor(target, bgNodes, false); + const outcome = axe.commons.color.getBackgroundColor( + target, + bgNodes, + false + ); assert.isNull(outcome); assert.equal(axe.commons.color.incompleteData.get('bgColor'), 'imgNode'); }); - it('returns elements with negative z-index', function () { - fixture.innerHTML = - '
' + - '
Some text
'; + it('returns elements with negative z-index', () => { + fixture.innerHTML = html` +
+
Some text
+ `; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target'), [] ); - var expected = new axe.commons.color.Color(0, 0, 0, 1); + const expected = new axe.commons.color.Color(0, 0, 0, 1); assertColorsClose(actual, expected); }); - it('returns negative z-index elements when body has a background', function () { - fixture.innerHTML = - '
' + - '
Some text
'; + it('returns negative z-index elements when body has a background', () => { + fixture.innerHTML = html` +
+
Some text
+ `; document.body.style.background = '#FFF'; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target'), [] ); - var expected = new axe.commons.color.Color(0, 0, 0, 1); + const expected = new axe.commons.color.Color(0, 0, 0, 1); assertColorsClose(actual, expected); }); - it('should return null for negative z-index element when html and body have a background', function () { - fixture.innerHTML = - '
' + - '
'; + it('should return null for negative z-index element when html and body have a background', () => { + fixture.innerHTML = html` +
+
+ `; document.documentElement.style.background = '#0F0'; document.body.style.background = '#FFF'; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target'), [] ); @@ -695,37 +910,43 @@ describe('color.getBackgroundColor', function () { assert.isNull(actual); }); - it('should return background color for inline elements that do not fit the viewport', function () { - var html = ''; + it('should return background color for inline elements that do not fit the viewport', () => { + let body = ''; for (var i = 0; i < 300; i++) { - html += 'foo
'; + body += 'foo
'; } - fixture.innerHTML = '' + html + ''; + fixture.innerHTML = html`${body}`; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(fixture, []); - var expected = new axe.commons.color.Color(255, 255, 255, 1); + const actual = axe.commons.color.getBackgroundColor(fixture, []); + const expected = new axe.commons.color.Color(255, 255, 255, 1); assertColorsClose(actual, expected); }); - it('should return the body bgColor when content does not overlap', function () { - fixture.innerHTML = - '
' + - '
Text' + - '
'; + it('should return the body bgColor when content does not overlap', () => { + fixture.innerHTML = html` +
+
+ Text +
+
+ `; axe.testUtils.flatTreeSetup(fixture); - var target = fixture.querySelector('#target'); - var actual = axe.commons.color.getBackgroundColor(target, []); - var expected = new axe.commons.color.Color(255, 255, 255, 1); + const target = fixture.querySelector('#target'); + const actual = axe.commons.color.getBackgroundColor(target, []); + const expected = new axe.commons.color.Color(255, 255, 255, 1); assert.deepEqual(actual, expected); }); - it('should return the html canvas inherited from body bgColor when element content does not overlap with body', function () { + it('should return the html canvas inherited from body bgColor when element content does not overlap with body', () => { fixture.innerHTML = '
Text
'; // size body element so that target element is positioned outside of background - var originalHeight = document.body.style.height; - var originalMargin = document.body.style.margin; + const originalHeight = document.body.style.height; + const originalMargin = document.body.style.margin; document.body.style.height = '1px'; document.body.style.background = '#000'; document.body.style.margin = 0; @@ -742,12 +963,12 @@ describe('color.getBackgroundColor', function () { } }); - it('should return the html canvas bgColor when element content does not overlap with body', function () { + it('should return the html canvas bgColor when element content does not overlap with body', () => { fixture.innerHTML = '
Text
'; // size body element so that target element is positioned outside of background - var originalHeight = document.body.style.height; + const originalHeight = document.body.style.height; document.body.style.height = '1px'; document.body.style.background = '#0f0'; document.documentElement.style.background = '#f00'; @@ -763,18 +984,23 @@ describe('color.getBackgroundColor', function () { } }); - it('should apply mix-blend-mode', function () { - fixture.innerHTML = ` + it('should apply mix-blend-mode', () => { + fixture.innerHTML = html`
-
exclusion1
+
+ exclusion1 +
`; axe.testUtils.flatTreeSetup(fixture); - var target = fixture.querySelector('#target'); - var actual = axe.commons.color.getBackgroundColor(target, []); + const target = fixture.querySelector('#target'); + const actual = axe.commons.color.getBackgroundColor(target, []); assert.closeTo(actual.red, 128, 0); assert.closeTo(actual.green, 223, 0); @@ -782,327 +1008,355 @@ describe('color.getBackgroundColor', function () { assert.closeTo(actual.alpha, 1, 0); }); - (shadowSupported ? it : xit)( - 'finds colors in shadow boundaries', - function () { - fixture.innerHTML = '
'; - var container = fixture.querySelector('#container'); - var shadow = container.attachShadow({ mode: 'open' }); - shadow.innerHTML = - '
' + - 'Text' + - '
'; - axe.testUtils.flatTreeSetup(fixture); + it('finds colors in shadow boundaries', () => { + fixture.innerHTML = '
'; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); + shadow.innerHTML = html` +
+ Text +
+ `; + axe.testUtils.flatTreeSetup(fixture); - var target = shadow.querySelector('#shadowTarget'); - var actual = axe.commons.color.getBackgroundColor(target, []); + const target = shadow.querySelector('#shadowTarget'); + const actual = axe.commons.color.getBackgroundColor(target, []); - var expected = new axe.commons.color.Color(0, 0, 0, 1); - assert.deepEqual(actual, expected); - } - ); + const expected = new axe.commons.color.Color(0, 0, 0, 1); + assert.deepEqual(actual, expected); + }); - (shadowSupported ? it : xit)( - 'finds colors across shadow boundaries', - function () { - fixture.innerHTML = - '
'; - var container = fixture.querySelector('#container'); - var shadow = container.attachShadow({ mode: 'open' }); - shadow.innerHTML = - 'Text'; - axe.testUtils.flatTreeSetup(fixture); + it('finds colors across shadow boundaries', () => { + fixture.innerHTML = + '
'; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); + shadow.innerHTML = + 'Text'; + axe.testUtils.flatTreeSetup(fixture); - var target = shadow.querySelector('#shadowTarget'); - var actual = axe.commons.color.getBackgroundColor(target, [], false); - var expected = new axe.commons.color.Color(0, 0, 0, 1); - assert.deepEqual(actual, expected); - } - ); + const target = shadow.querySelector('#shadowTarget'); + const actual = axe.commons.color.getBackgroundColor(target, [], false); + const expected = new axe.commons.color.Color(0, 0, 0, 1); + assert.deepEqual(actual, expected); + }); - (shadowSupported ? it : xit)( - 'should count an implicit label as a background element inside shadow dom', - function () { - fixture.innerHTML = '
'; - var container = fixture.querySelector('#container'); - var shadow = container.attachShadow({ mode: 'open' }); - shadow.innerHTML = - '
'; + it('should count an implicit label as a background element inside shadow dom', () => { + fixture.innerHTML = '
'; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); + shadow.innerHTML = + '
'; - var target = shadow.querySelector('#target'); - axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, []); - var expected = new axe.commons.color.Color(0, 0, 0, 1); - assert.deepEqual(actual, expected); - } - ); + const target = shadow.querySelector('#target'); + axe.testUtils.flatTreeSetup(fixture); + const actual = axe.commons.color.getBackgroundColor(target, []); + const expected = new axe.commons.color.Color(0, 0, 0, 1); + assert.deepEqual(actual, expected); + }); - (shadowSupported ? it : xit)( - 'finds colors for absolutely positioned elements across shadow boundaries', - function () { - fixture.innerHTML = - '
'; - var container = fixture.querySelector('#container'); - var shadow = container.attachShadow({ mode: 'open' }); - shadow.innerHTML = - '
Text
'; - axe.testUtils.flatTreeSetup(fixture); + it('finds colors for absolutely positioned elements across shadow boundaries', () => { + fixture.innerHTML = + '
'; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); + shadow.innerHTML = + '
Text
'; + axe.testUtils.flatTreeSetup(fixture); - var target = shadow.querySelector('#shadowTarget'); - axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, []); - var expected = new axe.commons.color.Color(255, 255, 255, 1); - assert.deepEqual(actual, expected); - } - ); + const target = shadow.querySelector('#shadowTarget'); + axe.testUtils.flatTreeSetup(fixture); + const actual = axe.commons.color.getBackgroundColor(target, []); + const expected = new axe.commons.color.Color(255, 255, 255, 1); + assert.deepEqual(actual, expected); + }); - (shadowSupported ? it : xit)( - 'finds a color for absolutely positioned content when background is in shadow dom', - function () { - fixture.innerHTML = - '
' + - '
Text
'; - - var elm1 = document.querySelector('#elm1'); - var shadow1 = elm1.attachShadow({ mode: 'open' }); - shadow1.innerHTML = - '
'; - var elm2 = document.querySelector('#elm2'); - axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(elm2, []); - var expected = new axe.commons.color.Color(0, 0, 0, 1); - assert.deepEqual(actual, expected); - } - ); + it('finds a color for absolutely positioned content when background is in shadow dom', () => { + fixture.innerHTML = html` +
+
Text
+ `; - (shadowSupported ? it : xit)( - 'finds colors for content rendered across multiple shadow boundaries', - function () { - fixture.innerHTML = - '
' + - '
'; - - var elm1 = document.querySelector('#elm1'); - var shadow1 = elm1.attachShadow({ mode: 'open' }); - shadow1.innerHTML = - '
'; - var elm2 = document.querySelector('#elm2'); - var shadow2 = elm2.attachShadow({ mode: 'open' }); - shadow2.innerHTML = - '' + - '
' + - 'Text' + - '
'; - - var elm3 = shadow2.querySelector('#elm3'); - axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(elm3, []); - var expected = new axe.commons.color.Color(128, 128, 128, 1); - assertColorsClose(actual, expected, 2, 0); - } - ); + const elm1 = document.querySelector('#elm1'); + const shadow1 = elm1.attachShadow({ mode: 'open' }); + shadow1.innerHTML = + '
'; + const elm2 = document.querySelector('#elm2'); + axe.testUtils.flatTreeSetup(fixture); + const actual = axe.commons.color.getBackgroundColor(elm2, []); + const expected = new axe.commons.color.Color(0, 0, 0, 1); + assert.deepEqual(actual, expected); + }); - (shadowSupported ? it : xit)( - 'finds colors for multiline elements across shadow boundaries', - function () { - fixture.innerHTML = - '
'; - var container = fixture.querySelector('#container'); - var shadow = container.attachShadow({ mode: 'open' }); - shadow.innerHTML = - '
Text
More text
'; - axe.testUtils.flatTreeSetup(fixture); - var target = shadow.querySelector('#shadowTarget'); - var actual = axe.commons.color.getBackgroundColor(target, []); - var expected = new axe.commons.color.Color(0, 0, 0, 1); - assert.deepEqual(actual, expected); - } - ); + it('finds colors for content rendered across multiple shadow boundaries', () => { + fixture.innerHTML = html` +
+
+
+
+ `; + + const elm1 = document.querySelector('#elm1'); + const shadow1 = elm1.attachShadow({ mode: 'open' }); + shadow1.innerHTML = + '
'; + const elm2 = document.querySelector('#elm2'); + const shadow2 = elm2.attachShadow({ mode: 'open' }); + shadow2.innerHTML = html` +
+ Text +
+ `; + + const elm3 = shadow2.querySelector('#elm3'); + axe.testUtils.flatTreeSetup(fixture); + const actual = axe.commons.color.getBackgroundColor(elm3, []); + const expected = new axe.commons.color.Color(128, 128, 128, 1); + assertColorsClose(actual, expected, 2, 0); + }); + + it('finds colors for multiline elements across shadow boundaries', () => { + fixture.innerHTML = + '
'; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); + shadow.innerHTML = + '
Text
More text
'; + axe.testUtils.flatTreeSetup(fixture); + const target = shadow.querySelector('#shadowTarget'); + const actual = axe.commons.color.getBackgroundColor(target, []); + const expected = new axe.commons.color.Color(0, 0, 0, 1); + assert.deepEqual(actual, expected); + }); (shadowSupported ? xit : xit)( 'returns null for multiline elements not fully covering parents across shadow boundaries', - function () { + () => { fixture.innerHTML = '
'; - var container = fixture.querySelector('#container'); - var shadow = container.attachShadow({ mode: 'open' }); + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); shadow.innerHTML = '
Text
More text
'; axe.testUtils.flatTreeSetup(fixture); - var target = shadow.querySelector('#shadowTarget'); - var actual = axe.commons.color.getBackgroundColor(target, []); + const target = shadow.querySelector('#shadowTarget'); + const actual = axe.commons.color.getBackgroundColor(target, []); assert.isNull(actual); } ); - (shadowSupported ? it : xit)( - 'returns a color for slotted content', - function () { - fixture.innerHTML = '
'; - var div = fixture.querySelector('#container'); - div.innerHTML = 'Link'; - var shadow = div.attachShadow({ mode: 'open' }); - shadow.innerHTML = '

'; - axe.testUtils.flatTreeSetup(fixture); - var linkElm = div.querySelector('a'); - var actual = axe.commons.color.getBackgroundColor(linkElm, []); - var expected = new axe.commons.color.Color(0, 0, 0, 1); - assert.deepEqual(actual, expected); - } - ); + it('returns a color for slotted content', () => { + fixture.innerHTML = '
'; + const div = fixture.querySelector('#container'); + div.innerHTML = 'Link'; + const shadow = div.attachShadow({ mode: 'open' }); + shadow.innerHTML = '

'; + axe.testUtils.flatTreeSetup(fixture); + const linkElm = div.querySelector('a'); + const actual = axe.commons.color.getBackgroundColor(linkElm, []); + const expected = new axe.commons.color.Color(0, 0, 0, 1); + assert.deepEqual(actual, expected); + }); - it('should return the text-shadow mixed in with the background', function () { - fixture.innerHTML = - '
' + - '
foo' + - '
'; - var target = fixture.querySelector('#target'); - var parent = fixture.querySelector('#parent'); - var bgNodes = []; + it('should return the text-shadow mixed in with the background', () => { + fixture.innerHTML = html` +
+
+ foo +
+
+ `; + const target = fixture.querySelector('#target'); + const parent = fixture.querySelector('#parent'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); // is 128 without the shadow - var expected = new axe.commons.color.Color(145, 0, 0, 1); + const expected = new axe.commons.color.Color(145, 0, 0, 1); assertColorsClose(actual, expected); assert.deepEqual(bgNodes, [parent]); }); - it('ignores thin text-shadows', function () { - fixture.innerHTML = - '
' + - '
foo' + - '
'; - var target = fixture.querySelector('#target'); - var bgNodes = []; + it('ignores thin text-shadows', () => { + fixture.innerHTML = html` +
+
+ foo +
+
+ `; + const target = fixture.querySelector('#target'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); - var expected = new axe.commons.color.Color(0, 0, 0, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const expected = new axe.commons.color.Color(0, 0, 0, 1); assert.deepEqual(actual, expected); }); it('filters small shadows, while keeping larger ones', () => { - fixture.innerHTML = ` -
-
foo -
+ fixture.innerHTML = html` +
+
+ foo +
+
`; - var target = fixture.querySelector('#target'); - var parent = fixture.querySelector('#parent'); - var bgNodes = []; + const target = fixture.querySelector('#target'); + const parent = fixture.querySelector('#parent'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes); // is 128 without the shadow - var expected = new axe.commons.color.Color(145, 0, 0, 1); + const expected = new axe.commons.color.Color(145, 0, 0, 1); assertColorsClose(actual, expected); assert.deepEqual(bgNodes, [parent]); }); - it('ignores text-shadows thinner than shadowOutlineEmMax', function () { - fixture.innerHTML = - '
' + - '
foo' + - '
'; - var target = fixture.querySelector('#target'); - var bgNodes = []; + it('ignores text-shadows thinner than shadowOutlineEmMax', () => { + fixture.innerHTML = html` +
+
+ foo +
+
+ `; + const target = fixture.querySelector('#target'); + const bgNodes = []; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor(target, bgNodes, 1); + const actual = axe.commons.color.getBackgroundColor(target, bgNodes, 1); // is 128 without the shadow - var expected = new axe.commons.color.Color(145, 0, 0, 1); + const expected = new axe.commons.color.Color(145, 0, 0, 1); + assertColorsClose(actual, expected); + }); + + it('calculates background of a textarea', () => { + fixture.innerHTML = + ''; + const target = fixture.querySelector('#target'); + axe.testUtils.flatTreeSetup(fixture); + const bgNodes = []; + const actual = axe.commons.color.getBackgroundColor(target, bgNodes, 1); + + const expected = new axe.commons.color.Color(255, 0, 0, 1); assertColorsClose(actual, expected); }); - describe('body and document', function () { - it('returns the body background', function () { + describe('body and document', () => { + it('returns the body background', () => { fixture.innerHTML = '
elm
'; document.body.style.background = '#F00'; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target'), [] ); - var expected = new axe.commons.color.Color(255, 0, 0, 1); + const expected = new axe.commons.color.Color(255, 0, 0, 1); assertColorsClose(actual, expected); }); - it('returns the body background even when the body is MUCH larger than the screen', function () { + it('returns the body background even when the body is MUCH larger than the screen', () => { fixture.innerHTML = '
elm
'; document.body.style.background = '#F00'; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target'), [] ); - var expected = new axe.commons.color.Color(255, 0, 0, 1); + const expected = new axe.commons.color.Color(255, 0, 0, 1); assertColorsClose(actual, expected); }); - it('returns the html background', function () { + it('returns the html background', () => { fixture.innerHTML = '
'; document.documentElement.style.background = '#0F0'; document.body.setAttribute('style', 'background: unset'); axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target'), [] ); document.body.removeAttribute('style'); - var expected = new axe.commons.color.Color(0, 255, 0, 1); + const expected = new axe.commons.color.Color(0, 255, 0, 1); assertColorsClose(actual, expected); }); - it('returns the html background when body does not cover the element', function () { + it('returns the html background when body does not cover the element', () => { fixture.innerHTML = '
elm
'; document.documentElement.style.background = '#0F0'; document.body.style.background = '#00F'; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target'), [] ); - var expected = new axe.commons.color.Color(0, 255, 0, 1); + const expected = new axe.commons.color.Color(0, 255, 0, 1); assertColorsClose(actual, expected); }); - it('returns the body background when body does cover the element', function () { + it('returns the body background when body does cover the element', () => { fixture.innerHTML = '
'; document.documentElement.style.background = '#0F0'; document.body.style.background = '#00F'; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target'), [] ); - var expected = new axe.commons.color.Color(0, 0, 255, 1); + const expected = new axe.commons.color.Color(0, 0, 255, 1); assertColorsClose(actual, expected); }); - it('returns both the html and body background if the body has alpha', function () { + it('returns both the html and body background if the body has alpha', () => { fixture.innerHTML = '
'; document.documentElement.style.background = '#0F0'; document.body.style.background = 'rgba(0, 0, 255, 0.5)'; axe.testUtils.flatTreeSetup(fixture); - var actual = axe.commons.color.getBackgroundColor( + const actual = axe.commons.color.getBackgroundColor( document.getElementById('target'), [] ); - var expected = new axe.commons.color.Color(0, 128, 128, 1); + const expected = new axe.commons.color.Color(0, 128, 128, 1); assertColorsClose(actual, expected); }); diff --git a/test/commons/color/get-contrast.js b/test/commons/color/get-contrast.js index 72919a3ac..02b28d374 100644 --- a/test/commons/color/get-contrast.js +++ b/test/commons/color/get-contrast.js @@ -1,11 +1,9 @@ -describe('color.getContrast', function () { - 'use strict'; - - it('should calculate contrast sensibly', function () { - var black = new axe.commons.color.Color(0, 0, 0, 1); - var transparent = new axe.commons.color.Color(0, 0, 0, 0); - var white = new axe.commons.color.Color(255, 255, 255, 1); - var yellow = new axe.commons.color.Color(255, 255, 0, 1); +describe('color.getContrast', () => { + it('should calculate contrast sensibly', () => { + const black = new axe.commons.color.Color(0, 0, 0, 1); + const transparent = new axe.commons.color.Color(0, 0, 0, 0); + const white = new axe.commons.color.Color(255, 255, 255, 1); + const yellow = new axe.commons.color.Color(255, 255, 0, 1); //Same foreground/background gives 1 assert.equal(axe.commons.color.getContrast(black, black), 1); diff --git a/test/commons/color/get-foreground-color.js b/test/commons/color/get-foreground-color.js index 06b31cc75..a22aba3f0 100644 --- a/test/commons/color/get-foreground-color.js +++ b/test/commons/color/get-foreground-color.js @@ -1,4 +1,5 @@ describe('color.getForegroundColor', () => { + const html = axe.testUtils.html; const { getForegroundColor, Color } = axe.commons.color; const { queryFixture, queryShadowFixture } = axe.testUtils; @@ -40,13 +41,19 @@ describe('color.getForegroundColor', () => { }); it('returns null if containing parent has a background image and is non-opaque', () => { - const target = queryFixture( - '
' + - '
' + - 'Hello World' + - '
' - ).actualNode; + const target = queryFixture(html` +
+
+ Hello World +
+
+ `).actualNode; assert.isNull(getForegroundColor(target)); assert.equal(axe.commons.color.incompleteData.get('fgColor'), 'bgImage'); }); @@ -99,25 +106,33 @@ describe('color.getForegroundColor', () => { describe('with transparency', () => { it('returns the blended color if it has alpha set', () => { - const target = queryFixture( - '
' + - '
' + - 'This is my text' + - '
' - ).actualNode; + const target = queryFixture(html` +
+
+ This is my text +
+
+ `).actualNode; const fgColor = getForegroundColor(target); assertSameColor(fgColor, new Color(32, 32, 64), 0.8); }); it('returns the blended color if it has opacity set', () => { - const target = queryFixture( - '
' + - '
' + - 'This is my text' + - '
' - ).actualNode; + const target = queryFixture(html` +
+
+ This is my text +
+
+ `).actualNode; const fgColor = getForegroundColor(target); assertSameColor(fgColor, new Color(64, 0, 64)); }); @@ -132,37 +147,50 @@ describe('color.getForegroundColor', () => { it('combines opacity with text stroke alpha color', () => { const target = queryFixture( - `
Hello world
` + html`
+ Hello world +
` ).actualNode; const fgColor = getForegroundColor(target); assertSameColor(fgColor, new Color(191, 255, 255), 0.8); }); it('takes into account parent opacity tree', () => { - const target = queryFixture( - '
' + - '
' + - '
' + - 'This is my text' + - '
' - ).actualNode; + const target = queryFixture(html` +
+
+
+ This is my text +
+
+
+ `).actualNode; const fgColor = getForegroundColor(target); assertSameColor(fgColor, new Color(119.5, 119.5, 119.5), 0.8); }); it('takes into account entire parent opacity tree', () => { - const target = queryFixture( - '
' + - '
' + - '
' + - '
' + - 'This is my text' + - '
' - ).actualNode; + const target = queryFixture(html` +
+
+
+
+ This is my text +
+
+
+
+ `).actualNode; const fgColor = getForegroundColor(target); assertSameColor(fgColor, new Color(119.5, 119.5, 119.5), 0.8); }); @@ -171,8 +199,11 @@ describe('color.getForegroundColor', () => { describe('test-shadow', () => { it('returns text shadow color if foreground is transparent', () => { const target = queryFixture( - `
-
+ html`
+
This is my text
` @@ -183,8 +214,11 @@ describe('color.getForegroundColor', () => { it('returns a mix of colors if there is a shadow, foreground with alpha < 1, and background color', () => { const target = queryFixture( - `
-
+ html`
+
This is my text
` @@ -209,11 +243,16 @@ describe('color.getForegroundColor', () => { it('applies opacity to text-shadow', () => { const target = queryFixture( - `
Hello world
` + html`
+ Hello world +
` ).actualNode; const fgColor = getForegroundColor(target); assertSameColor(fgColor, new Color(191, 255, 255), 0.8); diff --git a/test/commons/color/get-own-background-color.js b/test/commons/color/get-own-background-color.js index b93deac16..276c563e6 100644 --- a/test/commons/color/get-own-background-color.js +++ b/test/commons/color/get-own-background-color.js @@ -1,20 +1,20 @@ -describe('color.getOwnBackgroundColor', function () { - 'use strict'; +describe('color.getOwnBackgroundColor', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var queryFixture = axe.testUtils.queryFixture; - var getOwnBackgroundColor = axe.commons.color.getOwnBackgroundColor; + const fixture = document.getElementById('fixture'); + const queryFixture = axe.testUtils.queryFixture; + const getOwnBackgroundColor = axe.commons.color.getOwnBackgroundColor; - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; axe._tree = undefined; }); - it('returns `new axe.commons.color.Color` instance when no background is set', function () { - var vNode = queryFixture( - '
' + '
' - ); - var actual = getOwnBackgroundColor( + it('returns `new axe.commons.color.Color` instance when no background is set', () => { + const vNode = queryFixture(html` +
+ `); + const actual = getOwnBackgroundColor( window.getComputedStyle(vNode.actualNode) ); assert.equal(actual.red, 0); @@ -23,12 +23,14 @@ describe('color.getOwnBackgroundColor', function () { assert.equal(actual.alpha, 0); }); - it('returns color with rgba values of specified background-color value', function () { - var vNode = queryFixture( - '
' + - '
' - ); - var actual = getOwnBackgroundColor( + it('returns color with rgba values of specified background-color value', () => { + const vNode = queryFixture(html` +
+ `); + const actual = getOwnBackgroundColor( window.getComputedStyle(vNode.actualNode) ); assert.equal(actual.red, 255); @@ -37,12 +39,14 @@ describe('color.getOwnBackgroundColor', function () { assert.equal(actual.alpha, 1); }); - it('returns color with rgba values and alpha', function () { - var vNode = queryFixture( - '
' + - '
' - ); - var actual = getOwnBackgroundColor( + it('returns color with rgba values and alpha', () => { + const vNode = queryFixture(html` +
+ `); + const actual = getOwnBackgroundColor( window.getComputedStyle(vNode.actualNode) ); assert.equal(actual.red, 0); @@ -51,12 +55,14 @@ describe('color.getOwnBackgroundColor', function () { assert.equal(actual.alpha, 0.5); }); - it('returns color with rgba values and opacity (for blending)', function () { - var vNode = queryFixture( - '
' + - '
' - ); - var actual = getOwnBackgroundColor( + it('returns color with rgba values and opacity (for blending)', () => { + const vNode = queryFixture(html` +
+ `); + const actual = getOwnBackgroundColor( window.getComputedStyle(vNode.actualNode) ); assert.equal(actual.red, 0); @@ -65,12 +71,14 @@ describe('color.getOwnBackgroundColor', function () { assert.equal(actual.alpha, 0.5); }); - it('returns color with rgba values, alpha and opacity', function () { - var vNode = queryFixture( - '
' + - '
' - ); - var actual = getOwnBackgroundColor( + it('returns color with rgba values, alpha and opacity', () => { + const vNode = queryFixture(html` +
+ `); + const actual = getOwnBackgroundColor( window.getComputedStyle(vNode.actualNode) ); assert.equal(actual.red, 0); diff --git a/test/commons/color/get-text-shadow-colors.js b/test/commons/color/get-text-shadow-colors.js index a9a790016..4857f95b3 100644 --- a/test/commons/color/get-text-shadow-colors.js +++ b/test/commons/color/get-text-shadow-colors.js @@ -1,28 +1,33 @@ -describe('axe.commons.color.getTextShadowColors', function () { - 'use strict'; +describe('axe.commons.color.getTextShadowColors', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var getTextShadowColors = axe.commons.color.getTextShadowColors; + const fixture = document.getElementById('fixture'); + const getTextShadowColors = axe.commons.color.getTextShadowColors; - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('returns an empty array when there is no text-shadow', function () { + it('returns an empty array when there is no text-shadow', () => { fixture.innerHTML = 'Hello world'; - var span = fixture.querySelector('span'); - var shadowColors = getTextShadowColors(span); + const span = fixture.querySelector('span'); + const shadowColors = getTextShadowColors(span); assert.lengthOf(shadowColors, 0); }); - it('returns a rgb values of each text-shadow color', function () { - fixture.innerHTML = - 'Hello world'; + it('returns a rgb values of each text-shadow color', () => { + fixture.innerHTML = html` + Hello world + `; - var span = fixture.querySelector('span'); - var shadowColors = getTextShadowColors(span); + const span = fixture.querySelector('span'); + const shadowColors = getTextShadowColors(span); assert.lengthOf(shadowColors, 3); assert.equal(shadowColors[0].red, 255); @@ -38,13 +43,17 @@ describe('axe.commons.color.getTextShadowColors', function () { assert.equal(shadowColors[2].blue, 0); }); - it('returns transparent if the blur radius is greater than the offset', function () { - fixture.innerHTML = - 'Hello world'; - var span = fixture.querySelector('span'); - var shadowColors = getTextShadowColors(span); + it('returns transparent if the blur radius is greater than the offset', () => { + fixture.innerHTML = html` + Hello world + `; + const span = fixture.querySelector('span'); + const shadowColors = getTextShadowColors(span); assert.lengthOf(shadowColors, 3); assert.equal(shadowColors[0].alpha, 0); @@ -52,17 +61,22 @@ describe('axe.commons.color.getTextShadowColors', function () { assert.equal(shadowColors[2].alpha, 0); }); - it('returns an estimated alpha value based on blur radius', function () { - fixture.innerHTML = - 'Hello world'; - - var span = fixture.querySelector('span'); - var shadowColors = getTextShadowColors(span); - var expected0 = 3.7 / (2 + 8); - var expected1 = 3.7 / (10 + 8); - var expected2 = 3.7 / (18 + 8); + it('returns an estimated alpha value based on blur radius', () => { + fixture.innerHTML = html` + Hello world + `; + + const span = fixture.querySelector('span'); + const shadowColors = getTextShadowColors(span); + const expected0 = 3.7 / (2 + 8); + const expected1 = 3.7 / (10 + 8); + const expected2 = 3.7 / (18 + 8); assert.lengthOf(shadowColors, 3); assert.closeTo(shadowColors[0].alpha, expected0, 0.05); @@ -70,14 +84,19 @@ describe('axe.commons.color.getTextShadowColors', function () { assert.closeTo(shadowColors[2].alpha, expected2, 0.05); }); - it('returns an alpha of 1 if blur radius is 0', function () { - fixture.innerHTML = - 'Hello world'; + it('returns an alpha of 1 if blur radius is 0', () => { + fixture.innerHTML = html` + Hello world + `; - var span = fixture.querySelector('span'); - var shadowColors = getTextShadowColors(span); + const span = fixture.querySelector('span'); + const shadowColors = getTextShadowColors(span); assert.lengthOf(shadowColors, 4); assert.equal(shadowColors[0].alpha, 1); @@ -86,30 +105,38 @@ describe('axe.commons.color.getTextShadowColors', function () { assert.equal(shadowColors[3].alpha, 1); }); - it('handles floating point values', function () { - fixture.innerHTML = - 'Hello world'; + it('handles floating point values', () => { + fixture.innerHTML = html` + Hello world + `; - var span = fixture.querySelector('span'); - var shadowColors = getTextShadowColors(span); - var expectedAlpha = 3.7 / (0.12 + 8); + const span = fixture.querySelector('span'); + const shadowColors = getTextShadowColors(span); + const expectedAlpha = 3.7 / (0.12 + 8); assert.lengthOf(shadowColors, 1); assert.closeTo(shadowColors[0].alpha, expectedAlpha, 0.01); }); - it('combines the blur radius alpha with the alpha of the text-shadow color', function () { - fixture.innerHTML = - 'Hello world'; - - var span = fixture.querySelector('span'); - var shadowColors = getTextShadowColors(span); - var expected1 = (3.7 / (2 + 8)) * 0.5; - var expected2 = (3.7 / (2 + 8)) * 0.8; + it('combines the blur radius alpha with the alpha of the text-shadow color', () => { + fixture.innerHTML = html` + Hello world + `; + + const span = fixture.querySelector('span'); + const shadowColors = getTextShadowColors(span); + const expected1 = (3.7 / (2 + 8)) * 0.5; + const expected2 = (3.7 / (2 + 8)) * 0.8; assert.lengthOf(shadowColors, 3); assert.closeTo(shadowColors[0].alpha, 0, 0.05); @@ -117,24 +144,34 @@ describe('axe.commons.color.getTextShadowColors', function () { assert.closeTo(shadowColors[2].alpha, expected2, 0.05); }); - it('treats the blur radius as 0 when left undefined', function () { - fixture.innerHTML = - 'Hello world'; + it('treats the blur radius as 0 when left undefined', () => { + fixture.innerHTML = html` + Hello world + `; - var span = fixture.querySelector('span'); - var shadowColors = getTextShadowColors(span); + const span = fixture.querySelector('span'); + const shadowColors = getTextShadowColors(span); assert.lengthOf(shadowColors, 1); assert.equal(shadowColors[0].alpha, 0); }); - it('uses text color if text-shadow color is ommitted', function () { - fixture.innerHTML = - 'Hello world'; - var span = fixture.querySelector('span'); - var shadowColors = getTextShadowColors(span); + it('uses text color if text-shadow color is ommitted', () => { + fixture.innerHTML = html` + Hello world + `; + const span = fixture.querySelector('span'); + const shadowColors = getTextShadowColors(span); assert.lengthOf(shadowColors, 1); assert.equal(shadowColors[0].red, 255); @@ -142,35 +179,43 @@ describe('axe.commons.color.getTextShadowColors', function () { assert.equal(shadowColors[0].blue, 0); }); - it('returns null if a shadows has a ratio less than minRatio', function () { - fixture.innerHTML = - 'Hello world'; - - var span = fixture.querySelector('span'); - var shadowColors = getTextShadowColors(span, { minRatio: 0.5 }); + it('returns null if a shadows has a ratio less than minRatio', () => { + fixture.innerHTML = html` + Hello world + `; + + const span = fixture.querySelector('span'); + const shadowColors = getTextShadowColors(span, { minRatio: 0.5 }); assert.isNull(shadowColors); }); - it('does not return shadows with a ratio less than maxRatio', function () { - fixture.innerHTML = - 'Hello world'; + it('does not return shadows with a ratio less than maxRatio', () => { + fixture.innerHTML = html` + Hello world + `; - var span = fixture.querySelector('span'); - var shadowColors = getTextShadowColors(span, { maxRatio: 0.5 }); + const span = fixture.querySelector('span'); + const shadowColors = getTextShadowColors(span, { maxRatio: 0.5 }); assert.lengthOf(shadowColors, 1); assert.equal(shadowColors[0].blue, 255); }); - it('returns a transparent shadow when x offset is greater than blur', function () { + it('returns a transparent shadow when x offset is greater than blur', () => { fixture.innerHTML = 'Hello world'; - var span = fixture.querySelector('span'); - var shadowColors = getTextShadowColors(span); + const span = fixture.querySelector('span'); + const shadowColors = getTextShadowColors(span); assert.lengthOf(shadowColors, 1); assert.equal(shadowColors[0].red, 0); assert.equal(shadowColors[0].green, 0); @@ -178,11 +223,11 @@ describe('axe.commons.color.getTextShadowColors', function () { assert.equal(shadowColors[0].alpha, 0); }); - it('returns a transparent shadow when y offset is greater than blur', function () { + it('returns a transparent shadow when y offset is greater than blur', () => { fixture.innerHTML = 'Hello world'; - var span = fixture.querySelector('span'); - var shadowColors = getTextShadowColors(span); + const span = fixture.querySelector('span'); + const shadowColors = getTextShadowColors(span); assert.lengthOf(shadowColors, 1); assert.equal(shadowColors[0].red, 0); assert.equal(shadowColors[0].green, 0); @@ -193,13 +238,16 @@ describe('axe.commons.color.getTextShadowColors', function () { describe('with shadows that combine to a stroke', () => { const opt = { minRatio: 0.01 }; it('combines multiple shadows without blur into a single stroke', () => { - fixture.innerHTML = ` - Hello world + fixture.innerHTML = html` + Hello world `; const shadowColors = getTextShadowColors(fixture.firstElementChild, opt); assert.lengthOf(shadowColors, 1); @@ -213,13 +261,16 @@ describe('axe.commons.color.getTextShadowColors', function () { it('only combines shadows thinner than minRatio', () => { const minRatio = 0.1; // .1em (has to be greater than 1px, or this will be null) - fixture.innerHTML = ` - Hello wold + fixture.innerHTML = html` + Hello wold `; const shadowColors = getTextShadowColors(fixture.firstElementChild, { minRatio @@ -228,13 +279,16 @@ describe('axe.commons.color.getTextShadowColors', function () { }); it('places the combined shadow in the correct order with shadows', () => { - fixture.innerHTML = ` - Hello wold + fixture.innerHTML = html` + Hello wold `; const shadowColors = getTextShadowColors(fixture.firstElementChild, opt); const hexColors = shadowColors.map(color => color.toHexString()[1]); @@ -242,25 +296,31 @@ describe('axe.commons.color.getTextShadowColors', function () { }); it('returns null when when thin shadows are not all the way around the text', () => { - fixture.innerHTML = ` - Hello wold + fixture.innerHTML = html` + Hello wold `; const shadowColors = getTextShadowColors(fixture.firstElementChild, opt); assert.isNull(shadowColors); }); it('ignores partial strokes with ignoreEdgeCount: true', () => { - fixture.innerHTML = ` - Hello wold + fixture.innerHTML = html` + Hello wold `; const shadowColors = getTextShadowColors(fixture.firstElementChild, { ...opt, diff --git a/test/commons/color/has-valid-contrast-ratio.js b/test/commons/color/has-valid-contrast-ratio.js index 45768af4a..08c5bc4c1 100644 --- a/test/commons/color/has-valid-contrast-ratio.js +++ b/test/commons/color/has-valid-contrast-ratio.js @@ -1,10 +1,8 @@ -describe('color.Color', function () { - 'use strict'; - - it('should give sensible results for WCAG compliance', function () { - var black = new axe.commons.color.Color(0, 0, 0, 1); - var white = new axe.commons.color.Color(255, 255, 255, 1); - var gray = new axe.commons.color.Color(128, 128, 128, 1); +describe('color.Color', () => { + it('should give sensible results for WCAG compliance', () => { + const black = new axe.commons.color.Color(0, 0, 0, 1); + const white = new axe.commons.color.Color(255, 255, 255, 1); + const gray = new axe.commons.color.Color(128, 128, 128, 1); assert.isTrue( axe.commons.color.hasValidContrastRatio(black, white, 8, false).isValid @@ -55,8 +53,8 @@ describe('color.Color', function () { ); }); - it('should count 1-1 ratios as visually hidden', function () { - var black = new axe.commons.color.Color(0, 0, 0, 1); + it('should count 1-1 ratios as visually hidden', () => { + const black = new axe.commons.color.Color(0, 0, 0, 1); assert.isFalse( axe.commons.color.hasValidContrastRatio(black, black, 16, true).isValid diff --git a/test/commons/color/incomplete-data.js b/test/commons/color/incomplete-data.js index d7d0ed738..c1343507d 100644 --- a/test/commons/color/incomplete-data.js +++ b/test/commons/color/incomplete-data.js @@ -1,36 +1,34 @@ -describe('axe.commons.color.incompleteData', function () { - 'use strict'; +describe('axe.commons.color.incompleteData', () => { + const fixture = document.getElementById('fixture'); - var fixture = document.getElementById('fixture'); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should be an object', function () { + it('should be an object', () => { assert.isObject(axe.commons.color.incompleteData); }); - it('should store a string with a key for the incomplete check', function () { - var node = document.createElement('div'); + it('should store a string with a key for the incomplete check', () => { + const node = document.createElement('div'); fixture.appendChild(node); axe.commons.color.incompleteData.set('fgColor', 'bgImage'); assert.equal(axe.commons.color.incompleteData.get('fgColor'), 'bgImage'); }); - it('should throw an error if key is not a string', function () { - assert.throws(function () { + it('should throw an error if key is not a string', () => { + assert.throws(() => { axe.commons.color.incompleteData.set(); }, /key must be a string/); }); - it('should store a reason that matches a list of possibilities', function () { + it('should store a reason that matches a list of possibilities', () => { axe.commons.color.incompleteData.set('bgColor', 'bgImage'); assert.equal(axe.commons.color.incompleteData.get('bgColor'), 'bgImage'); }); - it('should clear the data object on request', function () { + it('should clear the data object on request', () => { axe.commons.color.incompleteData.set('fgColor', 'bgOverlap'); axe.commons.color.incompleteData.clear(); assert.isUndefined( diff --git a/test/commons/color/stacking-context.js b/test/commons/color/stacking-context.js index d02d62e48..38c9c82de 100644 --- a/test/commons/color/stacking-context.js +++ b/test/commons/color/stacking-context.js @@ -1,4 +1,5 @@ describe('color.stackingContext', () => { + const html = axe.testUtils.html; const { Color, getStackingContext, stackingContextToColor } = axe.commons.color; const { getElementStack } = axe.commons.dom; @@ -41,7 +42,7 @@ describe('color.stackingContext', () => { }); it('creates a context for every node in the element stack', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World
@@ -80,7 +81,7 @@ describe('color.stackingContext', () => { }); it('nests contexts', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World
@@ -139,9 +140,11 @@ describe('color.stackingContext', () => { }); it('creates a context for ancestors that create a stacking context but are not in the element stack', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
-
Hello World
+
+ Hello World +
`); const elmStack = getElementStack(vNode.actualNode).slice(0, -1); diff --git a/test/commons/dom/create-grid.js b/test/commons/dom/create-grid.js index 6073a3357..2cc304793 100644 --- a/test/commons/dom/create-grid.js +++ b/test/commons/dom/create-grid.js @@ -1,6 +1,7 @@ // Additional tests for createGrid are part of createRectStack tests, // which is what createGrid was originally part of describe('create-grid', () => { + const html = axe.testUtils.html; let fixture; const createGrid = axe.commons.dom.createGrid; const fixtureSetup = axe.testUtils.fixtureSetup; @@ -40,13 +41,14 @@ describe('create-grid', () => { }); it('adds large elements to multiple cell', () => { - fixture = fixtureSetup( - '' + - 'Hello world' - ); + fixture = fixtureSetup(html` + + Hello world + + `); createGrid(); - const positions = findPositions(fixture._grid, fixture.children[0]); + const positions = findPositions(fixture._grid, fixture.children[1]); assert.deepEqual(positions, [ { col: 0, row: 0 }, { col: 1, row: 0 }, @@ -56,10 +58,14 @@ describe('create-grid', () => { }); it('only adds the visible non-overflow area of large elements', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
- ' + - 'Hello world + + Hello world +
`); createGrid(); @@ -117,7 +123,7 @@ describe('create-grid', () => { }); it('creates multiple stacking context when they are nested', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello world
@@ -179,15 +185,19 @@ describe('create-grid', () => { it('adds elements vertically scrolled out of view', () => { const gridScroll = 2; - fixture = - fixtureSetup(`
+ fixture = fixtureSetup(html` +
V1
V2
V3
V4
V5
-
`); - const scroller = fixture.children[0]; +
+ `); + const scroller = fixture.children[1]; scroller.actualNode.scroll(0, gridSize * gridScroll); const childElms = scroller.children.filter( ({ props }) => props.nodeName === 'div' @@ -203,16 +213,17 @@ describe('create-grid', () => { it('adds elements horizontally scrolled out of view', () => { const gridScroll = 2; - fixture = - fixtureSetup(`
-
-
H1
-
H2
-
H3
-
H4
-
H5
-
-
`); + fixture = fixtureSetup( + html`
+
+
H1
+
H2
+
H3
+
H4
+
H5
+
+
` + ); const scroller = fixture.children[0]; scroller.actualNode.scroll(gridSize * gridScroll, 0); const childElms = scroller.children[0].children.filter( @@ -230,12 +241,12 @@ describe('create-grid', () => { describe('subGrids', () => { it('sets the .subGrid property', () => { - fixture = fixtureSetup( - '
' + - 'x' + - '
' - ); - const vOverflow = fixture.children[0]; + fixture = fixtureSetup(html` +
+ x +
+ `); + const vOverflow = fixture.children[1]; assert.isUndefined(vOverflow._subGrid); createGrid(); assert.isDefined(vOverflow._subGrid); @@ -243,38 +254,38 @@ describe('create-grid', () => { }); it('sets the ._grid of children as the subGrid', () => { - fixture = fixtureSetup( - '
' + - 'x' + - '
' - ); + fixture = fixtureSetup(html` +
+ x +
+ `); createGrid(); - const vOverflow = fixture.children[0]; - const vSpan = vOverflow.children[0]; + const vOverflow = fixture.children[1]; + const vSpan = vOverflow.children[1]; assert.equal(vOverflow._subGrid, vSpan._grid); }); it('does not add scrollable children to the root grid', () => { - fixture = fixtureSetup( - '
' + - 'x' + - '
' - ); + fixture = fixtureSetup(html` +
+ x +
+ `); createGrid(); - const vSpan = fixture.children[0].children[0]; + const vSpan = fixture.children[1].children[1]; const position = findPositions(fixture._grid, vSpan); assert.isEmpty(position); }); it('adds scrollable children to the subGrid', () => { - fixture = fixtureSetup( - '
' + - 'x' + - '
' - ); + fixture = fixtureSetup(html` +
+ x +
+ `); createGrid(); - const vOverflow = fixture.children[0]; - const vSpan = vOverflow.children[0]; + const vOverflow = fixture.children[1]; + const vSpan = vOverflow.children[1]; const position = findPositions(vOverflow._subGrid, vSpan); assert.deepEqual(position, [ { col: 0, row: 0 }, diff --git a/test/commons/dom/find-elms-in-context.js b/test/commons/dom/find-elms-in-context.js index 508b5e538..9c5337c09 100644 --- a/test/commons/dom/find-elms-in-context.js +++ b/test/commons/dom/find-elms-in-context.js @@ -1,17 +1,15 @@ -describe('dom.findElmsInContext', function () { - 'use strict'; - - var shadowSupport = axe.testUtils.shadowSupport; - var fixtureSetup = axe.testUtils.fixtureSetup; - var findElmsInContext = axe.commons.dom.findElmsInContext; - - it('returns an array or elements in the same context', function () { - var rootNode = fixtureSetup( - '1' + - '2' + - '3' + - '4' - ); +describe('dom.findElmsInContext', () => { + const html = axe.testUtils.html; + const fixtureSetup = axe.testUtils.fixtureSetup; + const findElmsInContext = axe.commons.dom.findElmsInContext; + + it('returns an array or elements in the same context', () => { + const rootNode = fixtureSetup(html` + 1 + 2 + 3 + 4 + `); assert.deepEqual( findElmsInContext({ @@ -24,44 +22,38 @@ describe('dom.findElmsInContext', function () { ); }); - (shadowSupport.v1 ? it : xit)( - 'ignores elements inside shadow tree', - function () { - var node = document.createElement('div'); - node.innerHTML = '1'; - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = '2 '; - var rootNode = fixtureSetup(node); - - var result = findElmsInContext({ - elm: 'b', - attr: 'name', - value: 'foo', - context: rootNode.actualNode - }); - assert.lengthOf(result, 1); - assert.equal(result[0].innerText, '1'); - } - ); - - (shadowSupport.v1 ? it : xit)( - 'can search elements limited to the shadow tree', - function () { - var node = document.createElement('div'); - node.innerHTML = '1'; - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = '2'; - fixtureSetup(node); - - var result = findElmsInContext({ - elm: 'b', - attr: 'name', - value: 'foo', - context: shadow - }); + it('ignores elements inside shadow tree', () => { + const node = document.createElement('div'); + node.innerHTML = '1'; + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = '2 '; + const rootNode = fixtureSetup(node); + + const result = findElmsInContext({ + elm: 'b', + attr: 'name', + value: 'foo', + context: rootNode.actualNode + }); + assert.lengthOf(result, 1); + assert.equal(result[0].innerText, '1'); + }); - assert.lengthOf(result, 1); - assert.equal(result[0].innerText, '2'); - } - ); + it('can search elements limited to the shadow tree', () => { + const node = document.createElement('div'); + node.innerHTML = '1'; + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = '2'; + fixtureSetup(node); + + const result = findElmsInContext({ + elm: 'b', + attr: 'name', + value: 'foo', + context: shadow + }); + + assert.lengthOf(result, 1); + assert.equal(result[0].innerText, '2'); + }); }); diff --git a/test/commons/dom/find-nearby-elms.js b/test/commons/dom/find-nearby-elms.js index 2bc46b92f..2c1bddf50 100644 --- a/test/commons/dom/find-nearby-elms.js +++ b/test/commons/dom/find-nearby-elms.js @@ -5,7 +5,7 @@ describe('findNearbyElms', () => { function getIds(vNodeList) { const ids = []; - vNodeList.forEach(function (vNode) { + vNodeList.forEach(vNode => { if (vNode.props.id && vNode.props.id !== 'fixture') { ids.push(vNode.props.id); } @@ -16,16 +16,16 @@ describe('findNearbyElms', () => { describe('in the viewport', () => { beforeEach(() => { fixture = fixtureSetup( - '
0
' + - '
1
' + - '
2
' + - '
3
' + - '
4
' + - '
5
' + - '
6
' + - '
7
' + - '
8
' + - '
9
' + `
0
` + + `
1
` + + `
2
` + + `
3
` + + `
4
` + + `
5
` + + `
6
` + + `
7
` + + `
8
` + + `
9
` ); }); @@ -43,9 +43,9 @@ describe('findNearbyElms', () => { describe('on the edge', () => { beforeEach(() => { fixture = fixtureSetup( - '
0
' + - '
1
' + - '
2
' + `
0
` + + `
1
` + + `
2
` ); }); @@ -68,12 +68,12 @@ describe('findNearbyElms', () => { describe('when some nodes are fixed', () => { beforeEach(() => { fixture = fixtureSetup( - '
' + - '
1
' + - '
2
' + - '
' + - '
3
' + - '
4
' + `
` + + `
1
` + + `
2
` + + `
` + + `
3
` + + `
4
` ); }); diff --git a/test/commons/dom/find-up.js b/test/commons/dom/find-up.js index 13c5d939a..7ddd5b6b8 100644 --- a/test/commons/dom/find-up.js +++ b/test/commons/dom/find-up.js @@ -1,24 +1,38 @@ /* global xit */ -describe('dom.findUp', function () { - 'use strict'; +describe('dom.findUp', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var shadowSupport = axe.testUtils.shadowSupport; + const fixture = document.getElementById('fixture'); + const shadowSupport = axe.testUtils.shadowSupport; - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; axe._tree = undefined; }); - it('should find parents based on selector', function () { - fixture.innerHTML = - '
' + - '
'; - - var start = document.getElementById('start'), + it('should find parents based on selector', () => { + fixture.innerHTML = html` +
+
+ + + +
+
+
+
+
+
+
+
+
+
+ `; + + const start = document.getElementById('start'), target = document.getElementById('target'); - axe.testUtils.flatTreeSetup(fixture.firstChild); + axe.testUtils.flatTreeSetup(fixture.firstElementChild); assert.equal( axe.commons.dom.findUp(start, '.target'), target, @@ -26,187 +40,172 @@ describe('dom.findUp', function () { ); }); - it('should return null if it cant find a match anywhere in the document', function () { + it('should return null if it cant find a match anywhere in the document', () => { fixture.innerHTML = '
'; - var start = document.getElementById('start'); + const start = document.getElementById('start'); - axe.testUtils.flatTreeSetup(fixture.firstChild); + axe.testUtils.flatTreeSetup(fixture.firstElementChild); assert.isNull(axe.commons.dom.findUp(start, '.nomatchyplzkthx')); }); - it('should return null if it cant find a match anywhere above the start', function () { + it('should return null if it cant find a match anywhere above the start', () => { fixture.innerHTML = '
'; - var start = document.getElementById('start'); + const start = document.getElementById('start'); - axe.testUtils.flatTreeSetup(fixture.firstChild); + axe.testUtils.flatTreeSetup(fixture.firstElementChild); assert.isNull(axe.commons.dom.findUp(start, '.target')); }); (shadowSupport.v0 ? it : xit)( 'should walk up the assigned content (v0)', - function () { + () => { function createContentSlotted() { - var group = document.createElement('div'); + const group = document.createElement('div'); group.innerHTML = ''; return group; } function makeShadowTree(node) { - var root = node.createShadowRoot(); - var div = document.createElement('div'); + const root = node.createShadowRoot(); + const div = document.createElement('div'); root.appendChild(div); div.appendChild(createContentSlotted()); } fixture.innerHTML = ''; makeShadowTree(fixture.querySelector('div')); - var tree = axe.testUtils.flatTreeSetup(fixture.firstChild); - var el = axe.utils.querySelectorAll(tree, 'a')[0]; + const tree = axe.testUtils.flatTreeSetup(fixture.firstElementChild); + const el = axe.utils.querySelectorAll(tree, 'a')[0]; assert.equal( axe.commons.dom.findUp(el.actualNode, 'label'), - fixture.firstChild + fixture.firstElementChild ); } ); - (shadowSupport.v0 ? it : xit)( - 'should walk down the shadow DOM v0', - function () { - function createContent() { - var group = document.createElement('div'); - group.innerHTML = 'thing'; - return group; - } - function makeShadowTree(node) { - var root = node.createShadowRoot(); - var div = document.createElement('div'); - div.appendChild(createContent()); - root.appendChild(div); - } - - fixture.innerHTML = ''; - makeShadowTree(fixture.querySelector('div')); - var tree = axe.testUtils.flatTreeSetup(fixture.firstChild); - var el = axe.utils.querySelectorAll(tree, 'a')[0]; - assert.equal( - axe.commons.dom.findUp(el.actualNode, 'label'), - fixture.firstChild - ); + (shadowSupport.v0 ? it : xit)('should walk down the shadow DOM v0', () => { + function createContent() { + const group = document.createElement('div'); + group.innerHTML = 'thing'; + return group; + } + function makeShadowTree(node) { + const root = node.createShadowRoot(); + const div = document.createElement('div'); + div.appendChild(createContent()); + root.appendChild(div); } - ); - (shadowSupport.v0 ? it : xit)( - 'should work on shadow root children', - function () { - fixture.innerHTML = '
'; - var shadow = fixture.querySelector('#something').createShadowRoot(); + fixture.innerHTML = ''; + makeShadowTree(fixture.querySelector('div')); + const tree = axe.testUtils.flatTreeSetup(fixture.firstElementChild); + const el = axe.utils.querySelectorAll(tree, 'a')[0]; + assert.equal( + axe.commons.dom.findUp(el.actualNode, 'label'), + fixture.firstElementChild + ); + }); - shadow.innerHTML = '
item 1
'; - var listItem = shadow.querySelector('[role=listitem]'); + (shadowSupport.v0 ? it : xit)('should work on shadow root children', () => { + fixture.innerHTML = '
'; + const shadow = fixture.querySelector('#something').createShadowRoot(); - axe.testUtils.flatTreeSetup(fixture.firstChild); - assert.equal( - axe.commons.dom.findUp(listItem, '[role=list]'), - fixture.firstChild - ); - } - ); + shadow.innerHTML = '
item 1
'; + const listItem = shadow.querySelector('[role=listitem]'); - (shadowSupport.v1 ? it : xit)( - 'should walk up the assigned slot', - function () { - function createContentSlotted() { - var group = document.createElement('div'); - group.innerHTML = - ''; - return group; - } - function makeShadowTree(node) { - var root = node.attachShadow({ mode: 'open' }); - var div = document.createElement('div'); - root.appendChild(div); - div.appendChild(createContentSlotted()); - } + axe.testUtils.flatTreeSetup(fixture.firstElementChild); + assert.equal( + axe.commons.dom.findUp(listItem, '[role=list]'), + fixture.firstElementChild + ); + }); - fixture.innerHTML = ''; - makeShadowTree(fixture.querySelector('div')); - var tree = axe.testUtils.flatTreeSetup(fixture.firstChild); - var el = axe.utils.querySelectorAll(tree, 'a')[0]; - assert.equal( - axe.commons.dom.findUp(el.actualNode, 'label'), - fixture.firstChild - ); + it('should walk up the assigned slot', () => { + function createContentSlotted() { + const group = document.createElement('div'); + group.innerHTML = + ''; + return group; + } + function makeShadowTree(node) { + const root = node.attachShadow({ mode: 'open' }); + const div = document.createElement('div'); + root.appendChild(div); + div.appendChild(createContentSlotted()); } - ); - (shadowSupport.v1 ? it : xit)( - 'should find element in assigned slot, not in the host document', - function () { - function createContentSlotted() { - var group = document.createElement('div'); - group.className = 'target'; - var slot = document.createElement('slot'); - group.appendChild(slot); - return group; - } - function makeShadowTree(node) { - var root = node.attachShadow({ mode: 'open' }); - var div = document.createElement('div'); - root.appendChild(div); - div.appendChild(createContentSlotted()); - } + fixture.innerHTML = ''; + makeShadowTree(fixture.querySelector('div')); + const tree = axe.testUtils.flatTreeSetup(fixture.firstElementChild); + const el = axe.utils.querySelectorAll(tree, 'a')[0]; + assert.equal( + axe.commons.dom.findUp(el.actualNode, 'label'), + fixture.firstElementChild + ); + }); - fixture.innerHTML = ''; - makeShadowTree(fixture.querySelector('div')); - var tree = axe.testUtils.flatTreeSetup(fixture.firstChild); - var el = axe.utils.querySelectorAll(tree, 'a')[0]; - var target = axe.utils.querySelectorAll(tree, '.target')[0]; - assert.equal( - axe.commons.dom.findUp(el.actualNode, 'div'), - target.actualNode - ); + it('should find element in assigned slot, not in the host document', () => { + function createContentSlotted() { + const group = document.createElement('div'); + group.className = 'target'; + const slot = document.createElement('slot'); + group.appendChild(slot); + return group; + } + function makeShadowTree(node) { + const root = node.attachShadow({ mode: 'open' }); + const div = document.createElement('div'); + root.appendChild(div); + div.appendChild(createContentSlotted()); } - ); - (shadowSupport.v1 ? it : xit)('should walk up the shadow DOM', function () { + fixture.innerHTML = ''; + makeShadowTree(fixture.querySelector('div')); + const tree = axe.testUtils.flatTreeSetup(fixture.firstElementChild); + const el = axe.utils.querySelectorAll(tree, 'a')[0]; + const target = axe.utils.querySelectorAll(tree, '.target')[0]; + assert.equal( + axe.commons.dom.findUp(el.actualNode, 'div'), + target.actualNode + ); + }); + + it('should walk up the shadow DOM', () => { function createContent() { - var group = document.createElement('div'); + const group = document.createElement('div'); group.innerHTML = 'thing'; return group; } function makeShadowTree(node) { - var root = node.attachShadow({ mode: 'open' }); - var div = document.createElement('div'); + const root = node.attachShadow({ mode: 'open' }); + const div = document.createElement('div'); root.appendChild(div); div.appendChild(createContent()); } fixture.innerHTML = ''; makeShadowTree(fixture.querySelector('div')); - var tree = axe.testUtils.flatTreeSetup(fixture.firstChild); - var el = axe.utils.querySelectorAll(tree, 'a')[0]; + const tree = axe.testUtils.flatTreeSetup(fixture.firstElementChild); + const el = axe.utils.querySelectorAll(tree, 'a')[0]; assert.equal( axe.commons.dom.findUp(el.actualNode, 'label'), - fixture.firstChild + fixture.firstElementChild ); }); - (shadowSupport.v1 ? it : xit)( - 'should work on shadow root children', - function () { - fixture.innerHTML = '
'; - var shadow = fixture - .querySelector('#something') - .attachShadow({ mode: 'open' }); + it('should work on shadow root children', () => { + fixture.innerHTML = '
'; + const shadow = fixture + .querySelector('#something') + .attachShadow({ mode: 'open' }); - shadow.innerHTML = '
item 1
'; - var listItem = shadow.querySelector('[role=listitem]'); + shadow.innerHTML = '
item 1
'; + const listItem = shadow.querySelector('[role=listitem]'); - axe.testUtils.flatTreeSetup(fixture.firstChild); - assert.equal( - axe.commons.dom.findUp(listItem, '[role=list]'), - fixture.firstChild - ); - } - ); + axe.testUtils.flatTreeSetup(fixture.firstElementChild); + assert.equal( + axe.commons.dom.findUp(listItem, '[role=list]'), + fixture.firstElementChild + ); + }); }); diff --git a/test/commons/dom/get-composed-parent.js b/test/commons/dom/get-composed-parent.js index bf71d0e54..095f0fbf6 100644 --- a/test/commons/dom/get-composed-parent.js +++ b/test/commons/dom/get-composed-parent.js @@ -1,88 +1,86 @@ /* global xit */ -describe('dom.getComposedParent', function () { - 'use strict'; - var getComposedParent = axe.commons.dom.getComposedParent; - var fixture = document.getElementById('fixture'); - var shadowSupport = axe.testUtils.shadowSupport.v1; +describe('dom.getComposedParent', () => { + const html = axe.testUtils.html; + const getComposedParent = axe.commons.dom.getComposedParent; + const fixture = document.getElementById('fixture'); + const shadowSupport = axe.testUtils.shadowSupport.v1; - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('returns the parentNode normally', function () { + it('returns the parentNode normally', () => { fixture.innerHTML = '
'; - var actual = getComposedParent(document.getElementById('target')); + const actual = getComposedParent(document.getElementById('target')); assert.instanceOf(actual, Node); assert.equal(actual, document.getElementById('parent')); }); - it('returns null from the documentElement', function () { + it('returns null from the documentElement', () => { assert.isNull(getComposedParent(document.documentElement)); }); - (shadowSupport ? it : xit)( - 'skip the slot node for slotted content', - function () { - fixture.innerHTML = '
'; - var shadowRoot = document - .getElementById('shadow') - .attachShadow({ mode: 'open' }); - shadowRoot.innerHTML = - '
' + '' + '
'; + (shadowSupport ? it : xit)('skip the slot node for slotted content', () => { + fixture.innerHTML = '
'; + const shadowRoot = document + .getElementById('shadow') + .attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = html` +
+ +
+ `; - var actual = getComposedParent(fixture.querySelector('#target')); - assert.instanceOf(actual, Node); - assert.equal(actual, shadowRoot.querySelector('#grand-parent')); - } - ); + const actual = getComposedParent(fixture.querySelector('#target')); + assert.instanceOf(actual, Node); + assert.equal(actual, shadowRoot.querySelector('#grand-parent')); + }); - (shadowSupport ? it : xit)( - 'understands explicitly slotted nodes', - function () { - fixture.innerHTML = - '
'; - var shadowRoot = document - .getElementById('shadow') - .attachShadow({ mode: 'open' }); - shadowRoot.innerHTML = - '
' + - '' + - '
' + - '
'; + (shadowSupport ? it : xit)('understands explicitly slotted nodes', () => { + fixture.innerHTML = + '
'; + const shadowRoot = document + .getElementById('shadow') + .attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = html` +
+ +
+
+ `; - var actual = getComposedParent(fixture.querySelector('#target')); - assert.instanceOf(actual, Node); - assert.equal(actual, shadowRoot.querySelector('#parent')); - } - ); + const actual = getComposedParent(fixture.querySelector('#target')); + assert.instanceOf(actual, Node); + assert.equal(actual, shadowRoot.querySelector('#parent')); + }); - (shadowSupport ? it : xit)( - 'returns elements within a shadow tree', - function () { - fixture.innerHTML = '
content
'; - var shadowRoot = document - .getElementById('shadow') - .attachShadow({ mode: 'open' }); - shadowRoot.innerHTML = - '
' + '' + '
'; + (shadowSupport ? it : xit)('returns elements within a shadow tree', () => { + fixture.innerHTML = '
content
'; + const shadowRoot = document + .getElementById('shadow') + .attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = html` +
+ +
+ `; - var actual = getComposedParent(shadowRoot.querySelector('#target')); - assert.instanceOf(actual, Node); - assert.equal(actual, shadowRoot.querySelector('#parent')); - } - ); + const actual = getComposedParent(shadowRoot.querySelector('#target')); + assert.instanceOf(actual, Node); + assert.equal(actual, shadowRoot.querySelector('#parent')); + }); (shadowSupport ? it : xit)( 'returns the host when it reaches the shadow root', - function () { + () => { fixture.innerHTML = '
content
'; - var shadowRoot = document + const shadowRoot = document .getElementById('parent') .attachShadow({ mode: 'open' }); shadowRoot.innerHTML = '
'; - var actual = getComposedParent(shadowRoot.querySelector('#target')); + const actual = getComposedParent(shadowRoot.querySelector('#target')); assert.instanceOf(actual, Node); assert.equal(actual, fixture.querySelector('#parent')); } diff --git a/test/commons/dom/get-element-by-reference.js b/test/commons/dom/get-element-by-reference.js index 6097d0c33..d0fa09f9f 100644 --- a/test/commons/dom/get-element-by-reference.js +++ b/test/commons/dom/get-element-by-reference.js @@ -1,120 +1,125 @@ -describe('dom.getElementByReference', function () { - 'use strict'; +describe('dom.getElementByReference', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); + const fixture = document.getElementById('fixture'); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should return null if the attribute is not found', function () { + it('should return null if the attribute is not found', () => { fixture.innerHTML = 'Hi'; - var node = document.getElementById('link'), + const node = document.getElementById('link'), result = axe.commons.dom.getElementByReference(node, 'usemap'); assert.isNull(result); }); - it('should return null if the attribute does not start with "#"', function () { + it('should return null if the attribute does not start with "#"', () => { fixture.innerHTML = 'Hi'; - var node = document.getElementById('link'), + const node = document.getElementById('link'), result = axe.commons.dom.getElementByReference(node, 'href'); assert.isNull(result); }); - it('should return null if no targets are found', function () { + it('should return null if no targets are found', () => { fixture.innerHTML = 'Hi'; - var node = document.getElementById('link'), + const node = document.getElementById('link'), result = axe.commons.dom.getElementByReference(node, 'href'); assert.isNull(result); }); - it('should return node if target is found (href)', function () { - fixture.innerHTML = - 'Hi' + ''; + it('should return node if target is found (href)', () => { + fixture.innerHTML = html` + Hi + + `; - var node = document.getElementById('link'), + const node = document.getElementById('link'), expected = document.getElementById('target'), result = axe.commons.dom.getElementByReference(node, 'href'); assert.equal(result, expected); }); - it('should return node if target is found (usemap)', function () { - fixture.innerHTML = - 'Hi' + ''; + it('should return node if target is found (usemap)', () => { + fixture.innerHTML = html` + Hi + + `; - var node = document.getElementById('link'), + const node = document.getElementById('link'), expected = document.getElementById('target'), result = axe.commons.dom.getElementByReference(node, 'usemap'); assert.equal(result, expected); }); - it('should prioritize ID', function () { - fixture.innerHTML = - 'Hi' + - '' + - ''; + it('should prioritize ID', () => { + fixture.innerHTML = html` + Hi + + + `; - var node = document.getElementById('link'), + const node = document.getElementById('link'), expected = document.getElementById('target'), result = axe.commons.dom.getElementByReference(node, 'href'); assert.equal(result, expected); }); - it('should fallback to name', function () { - fixture.innerHTML = - 'Hi' + - ''; + it('should fallback to name', () => { + fixture.innerHTML = html` + Hi + + `; - var node = document.getElementById('link'), + const node = document.getElementById('link'), expected = document.getElementById('target0'), result = axe.commons.dom.getElementByReference(node, 'href'); assert.equal(result, expected); }); - it('should return the first matching element with name', function () { - fixture.innerHTML = - 'Hi' + - '' + - ''; + it('should return the first matching element with name', () => { + fixture.innerHTML = html` + Hi + + + `; - var node = document.getElementById('link'), + const node = document.getElementById('link'), expected = document.getElementById('target0'), result = axe.commons.dom.getElementByReference(node, 'href'); assert.equal(result, expected); }); - it('returns the first matching element using Angular skiplinks', function () { - fixture.innerHTML = - 'Hi' + - '' + - ''; + it('returns the first matching element using Angular skiplinks', () => { + fixture.innerHTML = html` + Hi + + + `; - var node = document.getElementById('link'), + const node = document.getElementById('link'), expected = document.getElementById('target0'), result = axe.commons.dom.getElementByReference(node, 'href'); assert.equal(result, expected); }); - it('should work with absolute links', function () { - var currentPage = window.location.origin + window.location.pathname; + it('should work with absolute links', () => { + const currentPage = window.location.origin + window.location.pathname; - fixture.innerHTML = - 'Hi' + - '' + - ''; + fixture.innerHTML = html`Hi + + `; - var node = document.getElementById('link'), + const node = document.getElementById('link'), expected = document.getElementById('target'), result = axe.commons.dom.getElementByReference(node, 'href'); diff --git a/test/commons/dom/get-element-coordinates.js b/test/commons/dom/get-element-coordinates.js index 26b9efc9b..797e1aee2 100644 --- a/test/commons/dom/get-element-coordinates.js +++ b/test/commons/dom/get-element-coordinates.js @@ -1,20 +1,25 @@ //@todo better coverage -describe('dom.getElementCoordinates', function () { - 'use strict'; - var fixture = document.getElementById('fixture'); +describe('dom.getElementCoordinates', () => { + const html = axe.testUtils.html; + const fixture = document.getElementById('fixture'); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should calculate bounding box based on element position', function () { - var el, coords; + it('should calculate bounding box based on element position', () => { + let el, coords; - fixture.innerHTML = - '
' + - '' + - 'Absolute' + - '
'; + fixture.innerHTML = html` +
+ + Absolute +
+ `; el = document.getElementById('coords0'); coords = axe.commons.dom.getElementCoordinates(el); @@ -31,16 +36,21 @@ describe('dom.getElementCoordinates', function () { assert.equal(Math.round(coords.top), -1); }); - it('should take into account scroll offsets', function () { - var el, - coords, - offset = axe.commons.dom.getScrollOffset(window.document); + it('should take into account scroll offsets', () => { + let el; + let coords; + const offset = axe.commons.dom.getScrollOffset(window.document); - fixture.innerHTML = - '
' + - '' + - 'Absolute' + - '
'; + fixture.innerHTML = html` +
+ + Absolute +
+ `; el = document.getElementById('coords0'); coords = axe.commons.dom.getElementCoordinates(el); diff --git a/test/commons/dom/get-element-stack.js b/test/commons/dom/get-element-stack.js index 3b045f787..1dec6b075 100644 --- a/test/commons/dom/get-element-stack.js +++ b/test/commons/dom/get-element-stack.js @@ -1,16 +1,15 @@ describe('dom.getElementStack', () => { - 'use strict'; + const html = axe.testUtils.html; const fixture = document.getElementById('fixture'); const getElementStack = axe.commons.dom.getElementStack; - const shadowSupported = axe.testUtils.shadowSupport.v1; function mapToIDs(stack) { return stack - .map(function (node) { + .map(node => { return node.id; }) - .filter(function (id) { + .filter(id => { return !!id; }); } @@ -21,12 +20,13 @@ describe('dom.getElementStack', () => { describe('stack order', () => { it('should return stack in DOM order of non-positioned elements', () => { - fixture.innerHTML = - '
' + - '
' + - '

Hello World

' + - '
' + - '
'; + fixture.innerHTML = html` +
+
+

Hello World

+
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -34,13 +34,14 @@ describe('dom.getElementStack', () => { }); it('should not return elements outside of the stack', () => { - fixture.innerHTML = - '
' + - '
' + - 'Foo' + - '

Hello World

' + - '
' + - '
'; + fixture.innerHTML = html` +
+
+ Foo +

Hello World

+
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -48,13 +49,16 @@ describe('dom.getElementStack', () => { }); it('should return stack in DOM order of non-positioned elements with z-index', () => { - fixture.innerHTML = - '
' + - '
hello world
' + - '
' + - '
' + - '
Some text
' + - '
'; + fixture.innerHTML = html` +
+
+ hello world +
+
+
+
Some text
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -66,17 +70,23 @@ describe('dom.getElementStack', () => { it('should should handle positioned elements without z-index', () => { // see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/Stacking_without_z-index - fixture.innerHTML = - '
' + - 'DIV #1
position:absolute;
' + - '
' + - 'DIV #2
position:relative;
' + - '
' + - 'DIV #3
position:relative;
' + - '
' + - 'DIV #4
position:absolute;
' + - '
' + - 'DIV #5
position:static;
'; + fixture.innerHTML = html` +
+ DIV #1
position:absolute; +
+
+ DIV #2
position:relative; +
+
+ DIV #3
position:relative; +
+
+ DIV #4
position:absolute; +
+
+ DIV #5
position:static; +
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -85,15 +95,20 @@ describe('dom.getElementStack', () => { it('should handle floating and positioned elements without z-index', () => { // see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/Stacking_and_float - fixture.innerHTML = - '
' + - 'DIV #1
position:absolute;
' + - '
' + - 'DIV #2
float:left;
' + - '
' + - 'DIV #3
no positioning
' + - '
' + - 'DIV #4
position:absolute;
'; + fixture.innerHTML = html` +
+ DIV #1
position:absolute; +
+
+ DIV #2
float:left; +
+
+ DIV #3
no positioning +
+
+ DIV #4
position:absolute; +
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -101,13 +116,14 @@ describe('dom.getElementStack', () => { }); it('should handle floating parent elements', () => { - fixture.innerHTML = - '
' + - '
whole picture
' + - '
' + - '
' + - '
English
' + - '
'; + fixture.innerHTML = html` +
+
whole picture
+
+
+
English
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); @@ -117,26 +133,30 @@ describe('dom.getElementStack', () => { it('should handle z-index positioned elements in the same stacking context', () => { // see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/Stacking_context_example_1 - fixture.innerHTML = - '
' + - '
DIV #1' + - '
position:relative;' + - '
' + - '
DIV #2' + - '
position:absolute;' + - '
z-index:1;' + - '
' + - '
' + - '
' + - '
' + - '
DIV #3' + - '
position:relative;' + - '
' + - '
DIV #4' + - '
position:absolute;' + - '
z-index:2;' + - '
' + - '
'; + fixture.innerHTML = html` +
+
DIV #1
position:relative; +
+
DIV #2
position:absolute;
z-index:1; +
+
+
+
+
DIV #3
position:relative; +
+
DIV #4
position:absolute;
z-index:2; +
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -145,26 +165,30 @@ describe('dom.getElementStack', () => { it('should handle z-index positioned elements in different stacking contexts', () => { // see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/Stacking_context_example_2 - fixture.innerHTML = - '
' + - '
DIV #1' + - '
position:relative;' + - '
' + - '
DIV #2' + - '
position:absolute;' + - '
z-index:2;' + - '
' + - '
' + - '
' + - '
' + - '
DIV #3' + - '
position:relative;' + - '
' + - '
DIV #4' + - '
position:absolute;' + - '
z-index:10;' + - '
' + - '
'; + fixture.innerHTML = html` +
+
DIV #1
position:relative; +
+
DIV #2
position:absolute;
z-index:2; +
+
+
+
+
DIV #3
position:relative; +
+
DIV #4
position:absolute;
z-index:10; +
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -173,38 +197,57 @@ describe('dom.getElementStack', () => { it('should handle complex stacking context', () => { // see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context - fixture.innerHTML = - '
' + - 'Division Element #1
' + - 'position: relative;
' + - 'z-index: 5;' + - '
' + - '
' + - 'Division Element #2
' + - 'position: relative;
' + - 'z-index: 2;' + - '
' + - '
' + - '
' + - 'Division Element #4
' + - 'position: relative;
' + - 'z-index: 6;' + - '
' + - 'Division Element #3
' + - 'position: absolute;
' + - 'z-index: 4;' + - '
' + - 'Division Element #5
' + - 'position: relative;
' + - 'z-index: 1;' + - '
' + - '' + - '
' + - 'Division Element #6
' + - 'position: absolute;
' + - 'z-index: 3;' + - '
' + - '
'; + fixture.innerHTML = html` +
+ Division Element #1
+ position: relative;
+ z-index: 5; +
+
+ Division Element #2
+ position: relative;
+ z-index: 2; +
+
+
+ Division Element #4
+ position: relative;
+ z-index: 6; +
+ Division Element #3
+ position: absolute;
+ z-index: 4; +
+ Division Element #5
+ position: relative;
+ z-index: 1; +
+ +
+ Division Element #6
+ position: absolute;
+ z-index: 3; +
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -212,10 +255,11 @@ describe('dom.getElementStack', () => { }); it('should correctly order children of position elements without z-index', () => { - fixture.innerHTML = - '
' + - '
' + - '
'; + fixture.innerHTML = html` +
+
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -223,10 +267,11 @@ describe('dom.getElementStack', () => { }); it('should correctly order children of position elements with z-index', () => { - fixture.innerHTML = - '
' + - '
' + - '
'; + fixture.innerHTML = html` +
+
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -235,8 +280,11 @@ describe('dom.getElementStack', () => { ['flex', 'inline-flex', 'grid', 'inline-grid'].forEach(type => { it(`should correctly order "${type}" items with z-index`, () => { - fixture.innerHTML = ` -
+ fixture.innerHTML = html` +
@@ -249,13 +297,17 @@ describe('dom.getElementStack', () => { }); it('should handle modals on top of the stack', () => { - fixture.innerHTML = - '
' + - '
' + - '

Hello World

' + - '
' + - '
' + - '
'; + fixture.innerHTML = html` +
+
+

Hello World

+
+
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -263,13 +315,17 @@ describe('dom.getElementStack', () => { }); it('should handle "pointer-events:none"', () => { - fixture.innerHTML = - '
' + - '
' + - '

Hello World

' + - '
' + - '
' + - '
'; + fixture.innerHTML = html` +
+
+

Hello World

+
+
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -277,12 +333,13 @@ describe('dom.getElementStack', () => { }); it('should return elements left out by document.elementsFromPoint', () => { - fixture.innerHTML = - '
' + - '
' + - '' + - '
' + - '
'; + fixture.innerHTML = html` +
+
+ +
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -290,11 +347,17 @@ describe('dom.getElementStack', () => { }); it('should not return elements that do not fully cover the target', () => { - fixture.innerHTML = - '
' + - '
' + - '

Text oh heyyyy and here\'s
a link

' + - '
'; + fixture.innerHTML = html` +
+
+

+ Text oh heyyyy and here's
a link
+

+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -302,10 +365,11 @@ describe('dom.getElementStack', () => { }); it('should not return parent elements that do not fully cover the target', () => { - fixture.innerHTML = - '
' + - '
Text
' + - '
'; + fixture.innerHTML = html` +
+
Text
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -313,11 +377,14 @@ describe('dom.getElementStack', () => { }); it('should return elements that partially cover the target', () => { - fixture.innerHTML = - '
' + - '
' + - '
Text
' + - '
'; + fixture.innerHTML = html` +
+
+
+ Text +
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -325,12 +392,13 @@ describe('dom.getElementStack', () => { }); it('should handle negative z-index', () => { - fixture.innerHTML = - '
' + - '
' + - '

Hello World

' + - '
' + - '
'; + fixture.innerHTML = html` +
+
+

Hello World

+
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -338,13 +406,16 @@ describe('dom.getElementStack', () => { }); it('should not add hidden elements', () => { - fixture.innerHTML = - '
' + - '' + - '' + - '
Some text
' + - 'Hello World' + - '
'; + fixture.innerHTML = html` +
+ + +
Some text
+ Hello World +
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -352,13 +423,14 @@ describe('dom.getElementStack', () => { }); it('should correctly position children of positioned parents', () => { - fixture.innerHTML = - '
Some text
' + - '
' + - '
' + - '

Hi, Hello World.

' + - '
' + - '
'; + fixture.innerHTML = html` +
Some text
+
+
+

Hi, Hello World.

+
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -366,15 +438,18 @@ describe('dom.getElementStack', () => { }); it('should correctly position siblings with positioned children correctly', () => { - fixture.innerHTML = - '
Some text
' + - '
Some text
' + - '
' + - '
Some text
' + - '
' + - '
Some text
' + - '
' + - '
'; + fixture.innerHTML = html` +
Some text
+
Some text
+
+
+ Some text +
+
+
Some text
+
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -382,17 +457,24 @@ describe('dom.getElementStack', () => { }); it('should correctly position children of float elements with position elements', () => { - fixture.innerHTML = - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
'; + fixture.innerHTML = html` +
+
+
+
+
+
+
+
+
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -400,15 +482,18 @@ describe('dom.getElementStack', () => { }); it('should correctly position opacity elements and positioned elements', () => { - fixture.innerHTML = ` + fixture.innerHTML = html`
-

Hello World

+

Hello World

-
+
`; @@ -419,15 +504,18 @@ describe('dom.getElementStack', () => { }); it('should correctly order elements outside of the axe tree', () => { - fixture.innerHTML = ` + fixture.innerHTML = html`
-

Hello World

+

Hello World

-
+
`; @@ -438,13 +526,14 @@ describe('dom.getElementStack', () => { }); it('should return empty array for hidden elements', () => { - fixture.innerHTML = - '
' + - '' + - '
'; + fixture.innerHTML = html` +
+ +
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -452,13 +541,14 @@ describe('dom.getElementStack', () => { }); it('should return empty array for children of 0 height scrollable regions', () => { - fixture.innerHTML = - '
' + - '
' + - 'text' + - 'Hello World' + - '
' + - '
'; + fixture.innerHTML = html` +
+
+ text + Hello World +
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -466,13 +556,25 @@ describe('dom.getElementStack', () => { }); it('should correctly position children of different stacking contexts', () => { - fixture.innerHTML = ` -
+ fixture.innerHTML = html` +
-
+
-
-
+
+
Hello World
@@ -528,11 +630,12 @@ describe('dom.getElementStack', () => { }); it('should ignore element which exactly overlaps midpoint of target element', () => { - fixture.innerHTML = - '
' + - '

Foo

' + - 'Bar' + - '
'; + fixture.innerHTML = html` +
+

Foo

+ Bar +
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); @@ -543,110 +646,104 @@ describe('dom.getElementStack', () => { // IE11 either only supports clip paths defined by url() or not at all, // MDN and caniuse.com give different results... it('should not add hidden elements using clip-path', () => { - fixture.innerHTML = - '
' + - '
Some text
' + - 'Hello World' + - '
'; + fixture.innerHTML = html` +
+
+ Some text +
+ Hello World +
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); assert.deepEqual(stack, ['target', '1', 'fixture']); }); - (shadowSupported ? it : xit)( - 'should sort shadow dom elements correctly', - () => { - fixture.innerHTML = '
'; - const container = fixture.querySelector('#container'); - const shadow = container.attachShadow({ mode: 'open' }); - shadow.innerHTML = 'Text'; - axe.testUtils.flatTreeSetup(fixture); + it('should sort shadow dom elements correctly', () => { + fixture.innerHTML = '
'; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); + shadow.innerHTML = 'Text'; + axe.testUtils.flatTreeSetup(fixture); - const target = shadow.querySelector('#shadowTarget'); - const stack = mapToIDs(getElementStack(target)); - assert.deepEqual(stack, ['shadowTarget', 'container', 'fixture']); - } - ); - - (shadowSupported ? it : xit)( - 'should sort nested shadow dom elements correctly', - () => { - fixture.innerHTML = '
'; - const container = fixture.querySelector('#container'); - const shadow = container.attachShadow({ mode: 'open' }); - - shadow.innerHTML = '
'; - const nestedContainer = shadow.querySelector('#shadowContainer'); - const nestedShadow = nestedContainer.attachShadow({ mode: 'open' }); - - nestedShadow.innerHTML = 'Text'; - axe.testUtils.flatTreeSetup(fixture); + const target = shadow.querySelector('#shadowTarget'); + const stack = mapToIDs(getElementStack(target)); + assert.deepEqual(stack, ['shadowTarget', 'container', 'fixture']); + }); - const target = nestedShadow.querySelector('#shadowTarget'); - const stack = mapToIDs(getElementStack(target)); - assert.deepEqual(stack, [ - 'shadowTarget', - 'shadowContainer', - 'container', - 'fixture' - ]); - } - ); - - (shadowSupported ? it : xit)( - 'should sort positioned shadow elements correctly', - () => { - fixture.innerHTML = '
'; - const container = fixture.querySelector('#container'); - const shadow = container.attachShadow({ mode: 'open' }); - - shadow.innerHTML = - '
'; - const nestedContainer = shadow.querySelector('#shadowContainer'); - const nestedShadow = nestedContainer.attachShadow({ mode: 'open' }); - - nestedShadow.innerHTML = 'Text'; - axe.testUtils.flatTreeSetup(fixture); + it('should sort nested shadow dom elements correctly', () => { + fixture.innerHTML = '
'; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); - const target = nestedShadow.querySelector('#shadowTarget'); - const stack = mapToIDs(getElementStack(target)); - assert.deepEqual(stack, [ - 'container', - 'fixture', - 'shadowTarget', - 'shadowContainer' - ]); - } - ); - - (shadowSupported ? it : xit)( - 'should sort shadow elements with different trees correctly', - () => { - fixture.innerHTML = - '
'; - const container1 = fixture.querySelector('#container1'); - const shadow1 = container1.attachShadow({ mode: 'open' }); - shadow1.innerHTML = 'Text'; - - const container2 = fixture.querySelector('#container2'); - const shadow2 = container2.attachShadow({ mode: 'open' }); - shadow2.innerHTML = 'Container 2 text'; - axe.testUtils.flatTreeSetup(fixture); + shadow.innerHTML = '
'; + const nestedContainer = shadow.querySelector('#shadowContainer'); + const nestedShadow = nestedContainer.attachShadow({ mode: 'open' }); - const target = shadow1.querySelector('#shadowTarget'); - const stack = mapToIDs(getElementStack(target)); - assert.deepEqual(stack, [ - '1', - 'container2', - 'shadowTarget', - 'container1', - 'fixture' - ]); - } - ); - - (shadowSupported ? it : xit)('should sort elements', () => { + nestedShadow.innerHTML = 'Text'; + axe.testUtils.flatTreeSetup(fixture); + + const target = nestedShadow.querySelector('#shadowTarget'); + const stack = mapToIDs(getElementStack(target)); + assert.deepEqual(stack, [ + 'shadowTarget', + 'shadowContainer', + 'container', + 'fixture' + ]); + }); + + it('should sort positioned shadow elements correctly', () => { + fixture.innerHTML = '
'; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); + + shadow.innerHTML = + '
'; + const nestedContainer = shadow.querySelector('#shadowContainer'); + const nestedShadow = nestedContainer.attachShadow({ mode: 'open' }); + + nestedShadow.innerHTML = 'Text'; + axe.testUtils.flatTreeSetup(fixture); + + const target = nestedShadow.querySelector('#shadowTarget'); + const stack = mapToIDs(getElementStack(target)); + assert.deepEqual(stack, [ + 'container', + 'fixture', + 'shadowTarget', + 'shadowContainer' + ]); + }); + + it('should sort shadow elements with different trees correctly', () => { + fixture.innerHTML = + '
'; + const container1 = fixture.querySelector('#container1'); + const shadow1 = container1.attachShadow({ mode: 'open' }); + shadow1.innerHTML = 'Text'; + + const container2 = fixture.querySelector('#container2'); + const shadow2 = container2.attachShadow({ mode: 'open' }); + shadow2.innerHTML = 'Container 2 text'; + axe.testUtils.flatTreeSetup(fixture); + + const target = shadow1.querySelector('#shadowTarget'); + const stack = mapToIDs(getElementStack(target)); + assert.deepEqual(stack, [ + '1', + 'container2', + 'shadowTarget', + 'container1', + 'fixture' + ]); + }); + + it('should sort elements', () => { fixture.innerHTML = '

Slotted text

'; const container = fixture.querySelector('#container'); @@ -677,14 +774,15 @@ describe('dom.getElementStack', () => { }); it('should return stack of scroll regions', () => { - fixture.innerHTML = - '
' + - '
' + - '
' + - '

Hello World

' + - '
' + - '
' + - '
'; + fixture.innerHTML = html` +
+
+
+

Hello World

+
+
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -692,14 +790,15 @@ describe('dom.getElementStack', () => { }); it('should return stack when scroll region is larger than parent', () => { - fixture.innerHTML = - '
' + - '
' + - '
' + - '

Hello World

' + - '
' + - '
' + - '
'; + fixture.innerHTML = html` +
+
+
+

Hello World

+
+
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -707,18 +806,19 @@ describe('dom.getElementStack', () => { }); it('should return stack of recursive scroll regions', () => { - fixture.innerHTML = - '
' + - '
' + - '
' + - '
' + - '
' + - '

Hello World

' + - '
' + - '
' + - '
' + - '
' + - '
'; + fixture.innerHTML = html` +
+
+
+
+
+

Hello World

+
+
+
+
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); @@ -726,14 +826,15 @@ describe('dom.getElementStack', () => { }); it('should handle html as a scroll region', () => { - fixture.innerHTML = - '
' + - '
' + - '
' + - '

Hello World

' + - '
' + - '
' + - '
'; + fixture.innerHTML = html` +
+
+
+

Hello World

+
+
+
+ `; document.documentElement.style.height = '5000px'; document.documentElement.style.overflowY = 'scroll'; axe.testUtils.flatTreeSetup(fixture); @@ -743,14 +844,15 @@ describe('dom.getElementStack', () => { }); it('should use correct scroll region parent', () => { - fixture.innerHTML = - '
' + - '
' + - '
' + - '
text
' + - '
' + - '
' + - '
'; + fixture.innerHTML = html` +
+
+
+
text
+
+
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); const stack = mapToIDs(getElementStack(target)); diff --git a/test/commons/dom/get-modal-dialog.js b/test/commons/dom/get-modal-dialog.js index 08a5a09c2..c87133e52 100644 --- a/test/commons/dom/get-modal-dialog.js +++ b/test/commons/dom/get-modal-dialog.js @@ -1,12 +1,11 @@ describe('dom.getModalDialog', () => { + const html = axe.testUtils.html; const fixture = document.querySelector('#fixture'); const getModalDialog = axe.commons.dom.getModalDialog; const { flatTreeSetup } = axe.testUtils; it('returns a modal dialog', () => { - fixture.innerHTML = ` - Hello - `; + fixture.innerHTML = html` Hello `; document.querySelector('#target').showModal(); const tree = flatTreeSetup(fixture); const vNode = axe.utils.querySelectorAll(tree, '#target')[0]; @@ -15,35 +14,33 @@ describe('dom.getModalDialog', () => { }); it('returns null for an opened dialog', () => { - fixture.innerHTML = ` - Hello - `; + fixture.innerHTML = html` Hello `; flatTreeSetup(fixture); assert.isNull(getModalDialog()); }); it('returns null for a closed dialog', () => { - fixture.innerHTML = ` - Hello - `; + fixture.innerHTML = html` Hello `; flatTreeSetup(fixture); assert.isNull(getModalDialog()); }); it('returns null when there is no dialog', () => { - fixture.innerHTML = ` -
World
- `; + fixture.innerHTML = html`
World
`; flatTreeSetup(fixture); assert.isNull(getModalDialog()); }); it('returns null if the modal dialog is not visible', () => { - fixture.innerHTML = ` - + fixture.innerHTML = html` + Hello `; document.querySelector('#target').showModal(); @@ -54,8 +51,12 @@ describe('dom.getModalDialog', () => { describe('fallback', () => { it('returns true for modal dialog when elementsFromPoint does not return the dialog', () => { - fixture.innerHTML = ` - + fixture.innerHTML = html` + Hello
World
`; @@ -67,8 +68,15 @@ describe('dom.getModalDialog', () => { }); it('skips checking elements with pointer-events: none', () => { - fixture.innerHTML = ` - + fixture.innerHTML = html` + Hello
World
`; @@ -79,10 +87,15 @@ describe('dom.getModalDialog', () => { }); it('takes into account a scrolled page', () => { - fixture.innerHTML = ` + fixture.innerHTML = html`
Hello @@ -97,8 +110,12 @@ describe('dom.getModalDialog', () => { }); it('returns the modal dialog when two dialogs are open', () => { - fixture.innerHTML = ` - + fixture.innerHTML = html` + Hello Hello
World
@@ -111,8 +128,12 @@ describe('dom.getModalDialog', () => { }); it('returns the outer modal when a dialog modal contains a non-dialog modal', () => { - fixture.innerHTML = ` - + fixture.innerHTML = html` + Hello Open modal diff --git a/test/commons/dom/get-node-grid.js b/test/commons/dom/get-node-grid.js new file mode 100644 index 000000000..a64930c92 --- /dev/null +++ b/test/commons/dom/get-node-grid.js @@ -0,0 +1,27 @@ +describe('dom.getNodeGrid', () => { + const html = axe.testUtils.html; + const queryFixture = axe.testUtils.queryFixture; + const getNodeGrid = axe.commons.dom.getNodeGrid; + + it('returns the grid of an vNode', () => { + const vNode = queryFixture(html` +
+

text

+

text

+
+ `); + const grid = getNodeGrid(vNode); + assert.equal(grid.container.props.id, 'container'); + }); + + it('returns the grid of an elm', () => { + const vNode = queryFixture(html` +
+

text

+

text

+
+ `); + const grid = getNodeGrid(vNode.actualNode); + assert.equal(grid.container.props.id, 'container'); + }); +}); diff --git a/test/commons/dom/get-overflow-hidden-ancestors.js b/test/commons/dom/get-overflow-hidden-ancestors.js index 429c67ce4..4ea7d45db 100644 --- a/test/commons/dom/get-overflow-hidden-ancestors.js +++ b/test/commons/dom/get-overflow-hidden-ancestors.js @@ -1,4 +1,5 @@ describe('dom.getOverflowHiddenAncestors', () => { + const html = axe.testUtils.html; const { getOverflowHiddenAncestors } = axe.commons.dom; const { queryFixture } = axe.testUtils; @@ -7,7 +8,7 @@ describe('dom.getOverflowHiddenAncestors', () => { } it('returns parent node', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
@@ -18,7 +19,7 @@ describe('dom.getOverflowHiddenAncestors', () => { }); it('returns ancestor node', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
@@ -32,7 +33,7 @@ describe('dom.getOverflowHiddenAncestors', () => { it('returns itself', () => { const vNode = queryFixture( - `
` + html`
` ); const actual = getOverflowHiddenAncestors(vNode); @@ -40,7 +41,7 @@ describe('dom.getOverflowHiddenAncestors', () => { }); it('returns all nodes with overflow:hidden', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
@@ -56,7 +57,7 @@ describe('dom.getOverflowHiddenAncestors', () => { }); it('ignores other overflow types', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
diff --git a/test/commons/dom/get-scroll-offset.js b/test/commons/dom/get-scroll-offset.js index 5445ff4af..e014eb76d 100644 --- a/test/commons/dom/get-scroll-offset.js +++ b/test/commons/dom/get-scroll-offset.js @@ -1,8 +1,6 @@ -describe('dom.getScrollOffset', function () { - 'use strict'; - - it('should return scrollTop and scrollLeft for normal nodes', function () { - var offset = axe.commons.dom.getScrollOffset({ +describe('dom.getScrollOffset', () => { + it('should return scrollTop and scrollLeft for normal nodes', () => { + const offset = axe.commons.dom.getScrollOffset({ nodeType: 3, scrollTop: 42, scrollLeft: 98 @@ -12,8 +10,8 @@ describe('dom.getScrollOffset', function () { assert.equal(offset.top, 42); }); - it('should get the scroll from the documentElement if a document is passed in', function () { - var offset = axe.commons.dom.getScrollOffset({ + it('should get the scroll from the documentElement if a document is passed in', () => { + const offset = axe.commons.dom.getScrollOffset({ nodeType: 9, documentElement: { scrollTop: 42, @@ -25,8 +23,8 @@ describe('dom.getScrollOffset', function () { assert.equal(offset.top, 42); }); - it('should get the scroll from the document.body if a document is passed in and it has no documentElement', function () { - var offset = axe.commons.dom.getScrollOffset({ + it('should get the scroll from the document.body if a document is passed in and it has no documentElement', () => { + const offset = axe.commons.dom.getScrollOffset({ nodeType: 9, body: { scrollTop: 42, @@ -38,8 +36,8 @@ describe('dom.getScrollOffset', function () { assert.equal(offset.top, 42); }); - it('should work on a window object', function () { - var offset = axe.commons.dom.getScrollOffset({ + it('should work on a window object', () => { + const offset = axe.commons.dom.getScrollOffset({ document: { nodeType: 9, documentElement: { diff --git a/test/commons/dom/get-tabbable-elements.js b/test/commons/dom/get-tabbable-elements.js index 08254191e..1de98edf5 100644 --- a/test/commons/dom/get-tabbable-elements.js +++ b/test/commons/dom/get-tabbable-elements.js @@ -1,91 +1,88 @@ -describe('dom.getTabbableElements', function () { - 'use strict'; +describe('dom.getTabbableElements', () => { + const html = axe.testUtils.html; - var queryFixture = axe.testUtils.queryFixture; - var injectIntoFixture = axe.testUtils.injectIntoFixture; - var shadowSupported = axe.testUtils.shadowSupport.v1; - var getTabbableElementsFn = axe.commons.dom.getTabbableElements; + const queryFixture = axe.testUtils.queryFixture; + const injectIntoFixture = axe.testUtils.injectIntoFixture; + const getTabbableElementsFn = axe.commons.dom.getTabbableElements; - it('returns tabbable elms when node contains tabbable element', function () { - var virtualNode = queryFixture( - '
' + - '' + - '
' - ); - var actual = getTabbableElementsFn(virtualNode); + it('returns tabbable elms when node contains tabbable element', () => { + const virtualNode = queryFixture(html` +
+ +
+ `); + const actual = getTabbableElementsFn(virtualNode); assert.lengthOf(actual, 1); assert.equal(actual[0].actualNode.nodeName.toUpperCase(), 'TEXTAREA'); }); - it('returns empty [] when element does not contains tabbable element (using tabindex to take element out of tab-order)', function () { - var virtualNode = queryFixture( - '
' + '' + '
' - ); - var actual = getTabbableElementsFn(virtualNode); + it('returns empty [] when element does not contains tabbable element (using tabindex to take element out of tab-order)', () => { + const virtualNode = queryFixture(html` +
+ +
+ `); + const actual = getTabbableElementsFn(virtualNode); assert.lengthOf(actual, 0); }); - it('returns empty [] when element contains disabled (tabbable) element', function () { - var virtualNode = queryFixture( - '
' + '' + '
' - ); - var actual = getTabbableElementsFn(virtualNode); + it('returns empty [] when element contains disabled (tabbable) element', () => { + const virtualNode = queryFixture(html` +
+ +
+ `); + const actual = getTabbableElementsFn(virtualNode); assert.lengthOf(actual, 0); }); - it('returns empty [] when element does not contain tabbable element', function () { - var virtualNode = queryFixture( - '
' + '

Some text

' + '
' - ); - var actual = getTabbableElementsFn(virtualNode); + it('returns empty [] when element does not contain tabbable element', () => { + const virtualNode = queryFixture(html` +
+

Some text

+
+ `); + const actual = getTabbableElementsFn(virtualNode); assert.lengthOf(actual, 0); }); - (shadowSupported ? it : xit)( - 'returns tabbable elms when element contains tabbable element inside shadowDOM', - function () { - var fixture = injectIntoFixture('
`'); - var node = fixture.querySelector('#target'); - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = ''; - // re build tree after shadowDOM is constructed - axe.setup(fixture); - var virtualNode = axe.utils.getNodeFromTree(node); - var actual = getTabbableElementsFn(virtualNode); - assert.lengthOf(actual, 1); - assert.equal(actual[0].actualNode.nodeName.toUpperCase(), 'BUTTON'); - } - ); + it('returns tabbable elms when element contains tabbable element inside shadowDOM', () => { + const fixture = injectIntoFixture('
`'); + const node = fixture.querySelector('#target'); + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = ''; + // re build tree after shadowDOM is constructed + axe.setup(fixture); + const virtualNode = axe.utils.getNodeFromTree(node); + const actual = getTabbableElementsFn(virtualNode); + assert.lengthOf(actual, 1); + assert.equal(actual[0].actualNode.nodeName.toUpperCase(), 'BUTTON'); + }); - (shadowSupported ? it : xit)( - 'returns empty [] when element contains disabled (tabbable) element inside shadowDOM', - function () { - var fixture = injectIntoFixture('
`'); - var node = fixture.querySelector('#target'); - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = ''; - // re build tree after shadowDOM is constructed - axe.setup(fixture); - var virtualNode = axe.utils.getNodeFromTree(node); - var actual = getTabbableElementsFn(virtualNode); - assert.lengthOf(actual, 0); - } - ); + it('returns empty [] when element contains disabled (tabbable) element inside shadowDOM', () => { + const fixture = injectIntoFixture('
`'); + const node = fixture.querySelector('#target'); + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = ''; + // re build tree after shadowDOM is constructed + axe.setup(fixture); + const virtualNode = axe.utils.getNodeFromTree(node); + const actual = getTabbableElementsFn(virtualNode); + assert.lengthOf(actual, 0); + }); - (shadowSupported ? it : xit)( - 'returns empty [] when element does not contain tabbable element inside shadowDOM', - function () { - var fixture = injectIntoFixture('
`'); - var node = fixture.querySelector('#target'); - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = '

I am not tabbable

'; - // re build tree after shadowDOM is constructed - axe.setup(fixture); - var virtualNode = axe.utils.getNodeFromTree(node); - var actual = getTabbableElementsFn(virtualNode); - assert.lengthOf(actual, 0); - } - ); + it('returns empty [] when element does not contain tabbable element inside shadowDOM', () => { + const fixture = injectIntoFixture('
`'); + const node = fixture.querySelector('#target'); + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = '

I am not tabbable

'; + // re build tree after shadowDOM is constructed + axe.setup(fixture); + const virtualNode = axe.utils.getNodeFromTree(node); + const actual = getTabbableElementsFn(virtualNode); + assert.lengthOf(actual, 0); + }); }); diff --git a/test/commons/dom/get-target-rects.js b/test/commons/dom/get-target-rects.js index f25abe291..c69e60f4f 100644 --- a/test/commons/dom/get-target-rects.js +++ b/test/commons/dom/get-target-rects.js @@ -1,6 +1,8 @@ describe('get-target-rects', () => { + const html = axe.testUtils.html; const getTargetRects = axe.commons.dom.getTargetRects; const { queryFixture } = axe.testUtils; + const fixture = document.getElementById('fixture'); it('returns the bounding rect when unobscured', () => { const vNode = queryFixture(''); @@ -9,36 +11,62 @@ describe('get-target-rects', () => { }); it('returns subset rect when obscured', () => { - const vNode = queryFixture(` - -
+ const vNode = queryFixture(html` + +
`); const rects = getTargetRects(vNode); assert.deepEqual(rects, [new DOMRect(10, 5, 20, 40)]); }); it('ignores elements with "pointer-events: none"', () => { - const vNode = queryFixture(` - -
+ const vNode = queryFixture(html` + +
`); const rects = getTargetRects(vNode); assert.deepEqual(rects, [vNode.actualNode.getBoundingClientRect()]); }); it("ignores elements that don't overlap the target", () => { - const vNode = queryFixture(` - -
+ const vNode = queryFixture(html` + +
`); const rects = getTargetRects(vNode); assert.deepEqual(rects, [vNode.actualNode.getBoundingClientRect()]); }); it('ignores non-tabbable descendants of the target', () => { - const vNode = queryFixture(` - `); const rects = getTargetRects(vNode); @@ -46,10 +74,19 @@ describe('get-target-rects', () => { }); it('returns each unobscured area', () => { - const vNode = queryFixture(` - -
-
+ const vNode = queryFixture(html` + +
+
`); const rects = getTargetRects(vNode); assert.deepEqual(rects, [ @@ -59,25 +96,63 @@ describe('get-target-rects', () => { }); it('returns empty if target is fully obscured', () => { - const vNode = queryFixture(` - -
+ const vNode = queryFixture(html` + +
`); const rects = getTargetRects(vNode); assert.lengthOf(rects, 0); }); it('returns subset rect of the target with tabbable descendant', () => { - const vNode = queryFixture(` - `); const rects = getTargetRects(vNode); - console.log(JSON.stringify(rects)); assert.deepEqual(rects, [ new DOMRect(10, 5, 30, 7), new DOMRect(10, 5, 7, 40) ]); }); + + it('ignores non-tabbable descendants of the target that are in shadow dom', () => { + fixture.innerHTML = + ''; + const target = fixture.querySelector('#target'); + const shadow = fixture + .querySelector('#shadow') + .attachShadow({ mode: 'open' }); + shadow.innerHTML = + '
'; + + axe.setup(fixture); + const vNode = axe.utils.getNodeFromTree(target); + const rects = getTargetRects(vNode); + assert.deepEqual(rects, [vNode.actualNode.getBoundingClientRect()]); + }); + + it('uses client rects if target is inline', () => { + const vNode = queryFixture(html` + + `); + const rects = getTargetRects(vNode); + assert.deepEqual(rects, Array.from(vNode.actualNode.getClientRects())); + }); }); diff --git a/test/commons/dom/get-target-size.js b/test/commons/dom/get-target-size.js index 10bda5130..ebe9bce59 100644 --- a/test/commons/dom/get-target-size.js +++ b/test/commons/dom/get-target-size.js @@ -1,4 +1,5 @@ describe('get-target-size', () => { + const html = axe.testUtils.html; const getTargetSize = axe.commons.dom.getTargetSize; const { queryFixture } = axe.testUtils; @@ -9,37 +10,67 @@ describe('get-target-size', () => { }); it('returns target size when obscured', () => { - const vNode = queryFixture(` - -
+ const vNode = queryFixture(html` + +
`); const rect = getTargetSize(vNode); assert.deepEqual(rect, new DOMRect(10, 5, 20, 40)); }); it('ignores elements with "pointer-events: none"', () => { - const vNode = queryFixture(` - -
+ const vNode = queryFixture(html` + +
`); const rect = getTargetSize(vNode); assert.deepEqual(rect, vNode.actualNode.getBoundingClientRect()); }); it("ignores elements that don't overlap the target", () => { - const vNode = queryFixture(` - -
+ const vNode = queryFixture(html` + +
`); const rect = getTargetSize(vNode); assert.deepEqual(rect, vNode.actualNode.getBoundingClientRect()); }); it('returns the largest unobscured area', () => { - const vNode = queryFixture(` - -
-
+ const vNode = queryFixture(html` + +
+
`); const rect = getTargetSize(vNode); assert.deepEqual(rect, new DOMRect(10, 10, 20, 35)); diff --git a/test/commons/dom/get-text-element-stack.js b/test/commons/dom/get-text-element-stack.js index ed8c32258..110037afe 100644 --- a/test/commons/dom/get-text-element-stack.js +++ b/test/commons/dom/get-text-element-stack.js @@ -1,85 +1,119 @@ -describe('dom.getTextElementStack', function () { - 'use strict'; +describe('dom.getTextElementStack', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var getTextElementStack = axe.commons.dom.getTextElementStack; + const fixture = document.getElementById('fixture'); + const getTextElementStack = axe.commons.dom.getTextElementStack; function mapToIDs(stack) { return stack - .map(function (node) { + .map(node => { return node.id; }) - .filter(function (id) { + .filter(id => { return !!id; }); } - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should return array of client text rects', function () { - fixture.innerHTML = - '
' + - '
' + - 'Hello
World' + - '
' + - '
'; + it('should return array of client text rects', () => { + fixture.innerHTML = html` +
+
Hello
World
+
+ `; axe.testUtils.flatTreeSetup(fixture); - var target = fixture.querySelector('#target'); - var stacks = getTextElementStack(target).map(mapToIDs); + const target = fixture.querySelector('#target'); + const stacks = getTextElementStack(target).map(mapToIDs); assert.deepEqual(stacks, [['target', '1', 'fixture']]); }); - it('should ignore newline characters', function () { - fixture.innerHTML = - '
' + - '
' + - 'Hello
\n' + - 'World' + - '
' + - '
'; + it('should ignore newline characters', () => { + fixture.innerHTML = html` +
+
+ Hello
+ + World +
+
+ `; axe.testUtils.flatTreeSetup(fixture); - var target = fixture.querySelector('#target'); - var stacks = getTextElementStack(target).map(mapToIDs); + const target = fixture.querySelector('#target'); + const stacks = getTextElementStack(target).map(mapToIDs); assert.deepEqual(stacks, [['target', '1', 'fixture']]); }); - it('should handle truncated text', function () { - fixture.innerHTML = - '
' + - '
' + - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et sollicitudin quam. Fusce mi odio, egestas pulvinar erat eget, vehicula tempus est. Proin vitae ullamcorper velit. Donec sagittis est justo, mattis iaculis arcu facilisis id. Proin pulvinar ornare arcu a fermentum. Quisque et dignissim nulla, sit amet consectetur ipsum. Donec in libero porttitor, dapibus neque imperdiet, aliquam est. Vivamus blandit volutpat fringilla. In mi magna, mollis sit amet imperdiet eu, rutrum ut tellus. Mauris vel condimentum nibh, quis ultricies nisi. Vivamus accumsan quam mauris, id iaculis quam fringilla ac. Curabitur pulvinar dolor ac magna vehicula, non auctor ligula dignissim. Nam ac nibh porttitor, malesuada tortor varius, feugiat turpis. Mauris dapibus, tellus ut viverra porta, ipsum turpis bibendum ligula, at tempor felis ante non libero. Donec dapibus, diam sit amet posuere commodo, magna orci hendrerit ipsum, eu egestas mauris nulla ut ipsum. Sed luctus, orci in fringilla finibus, odio leo porta dolor, eu dignissim risus eros eget erat.' + - 'World' + - '
' + - '
'; + it('should handle truncated text', () => { + fixture.innerHTML = html` +
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et + sollicitudin quam. Fusce mi odio, egestas pulvinar erat eget, vehicula + tempus est. Proin vitae ullamcorper velit. Donec sagittis est justo, + mattis iaculis arcu facilisis id. Proin pulvinar ornare arcu a + fermentum. Quisque et dignissim nulla, sit amet consectetur ipsum. + Donec in libero porttitor, dapibus neque imperdiet, aliquam est. + Vivamus blandit volutpat fringilla. In mi magna, mollis sit amet + imperdiet eu, rutrum ut tellus. Mauris vel condimentum nibh, quis + ultricies nisi. Vivamus accumsan quam mauris, id iaculis quam + fringilla ac. Curabitur pulvinar dolor ac magna vehicula, non auctor + ligula dignissim. Nam ac nibh porttitor, malesuada tortor varius, + feugiat turpis. Mauris dapibus, tellus ut viverra porta, ipsum turpis + bibendum ligula, at tempor felis ante non libero. Donec dapibus, diam + sit amet posuere commodo, magna orci hendrerit ipsum, eu egestas + mauris nulla ut ipsum. Sed luctus, orci in fringilla finibus, odio leo + porta dolor, eu dignissim risus eros eget erat. World +
+
+ `; axe.testUtils.flatTreeSetup(fixture); - var target = fixture.querySelector('#target'); - var stacks = getTextElementStack(target).map(mapToIDs); + const target = fixture.querySelector('#target'); + const stacks = getTextElementStack(target).map(mapToIDs); assert.deepEqual(stacks, [['target', '1', 'fixture']]); }); - it('should handle text that is too large for the container', function () { - fixture.innerHTML = - '
' +
-      '\n\n' +
-      'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et sollicitudin quam. Fusce mi odio, egestas pulvinar erat eget, vehicula tempus est. Proin vitae ullamcorper velit. Donec sagittis est justo, mattis iaculis arcu facilisis id. Proin pulvinar ornare arcu a fermentum. Quisque et dignissim nulla, sit amet consectetur ipsum. Donec in libero porttitor, dapibus neque imperdiet, aliquam est. Vivamus blandit volutpat fringilla. In mi magna, mollis sit amet imperdiet eu, rutrum ut tellus. Mauris vel condimentum nibh, quis ultricies nisi. Vivamus accumsan quam mauris, id iaculis quam fringilla ac. Curabitur pulvinar dolor ac magna vehicula, non auctor ligula dignissim. Nam ac nibh porttitor, malesuada tortor varius, feugiat turpis. Mauris dapibus, tellus ut viverra porta, ipsum turpis bibendum ligula, at tempor felis ante non libero. Donec dapibus, diam sit amet posuere commodo, magna orci hendrerit ipsum, eu egestas mauris nulla ut ipsum. Sed luctus, orci in fringilla finibus, odio leo porta dolor, eu dignissim risus eros eget erat.' +
-      '' +
-      '
'; + it('should handle text that is too large for the container', () => { + fixture.innerHTML = html` +
+      
+
+
+      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et sollicitudin quam. Fusce mi odio, egestas pulvinar erat eget, vehicula tempus est. Proin vitae ullamcorper velit. Donec sagittis est justo, mattis iaculis arcu facilisis id. Proin pulvinar ornare arcu a fermentum. Quisque et dignissim nulla, sit amet consectetur ipsum. Donec in libero porttitor, dapibus neque imperdiet, aliquam est. Vivamus blandit volutpat fringilla. In mi magna, mollis sit amet imperdiet eu, rutrum ut tellus. Mauris vel condimentum nibh, quis ultricies nisi. Vivamus accumsan quam mauris, id iaculis quam fringilla ac. Curabitur pulvinar dolor ac magna vehicula, non auctor ligula dignissim. Nam ac nibh porttitor, malesuada tortor varius, feugiat turpis. Mauris dapibus, tellus ut viverra porta, ipsum turpis bibendum ligula, at tempor felis ante non libero. Donec dapibus, diam sit amet posuere commodo, magna orci hendrerit ipsum, eu egestas mauris nulla ut ipsum. Sed luctus, orci in fringilla finibus, odio leo porta dolor, eu dignissim risus eros eget erat.
+      
+      
+ `; axe.testUtils.flatTreeSetup(fixture); - var target = fixture.querySelector('#target'); - var stacks = getTextElementStack(target).map(mapToIDs); + const target = fixture.querySelector('#target'); + const stacks = getTextElementStack(target).map(mapToIDs); assert.deepEqual(stacks, [['target', '1', 'fixture']]); }); - it('should handle text that overflows outside the parent', function () { - fixture.innerHTML = - '
' + - '

The Chandni Chowk (Moonlight Square) is one of the oldest and busiest markets in Old Delhi, India. Chandni Chowk is located close to Old Delhi Railway Station. The Red Fort monument is located within the market. It was built in the 17th century by Mughal Emperor of India Shah Jahan and designed by his daughter Jahanara. The market was once divided by canals (now closed) to reflect moonlight and remains one of India\'s largest wholesale markets.

' + - '
'; + it('should handle text that overflows outside the parent', () => { + fixture.innerHTML = html` +
+

+ The Chandni Chowk (Moonlight Square) is one of the oldest and busiest + markets in Old Delhi, India. Chandni Chowk is located close to Old + Delhi Railway Station. The Red Fort monument is located within the + market. It was built in the 17th century by Mughal Emperor of India + Shah Jahan and designed by his daughter Jahanara. The market was once + divided by canals (now closed) to reflect moonlight and remains one of + India's largest wholesale markets. +

+
+ `; axe.testUtils.flatTreeSetup(fixture); - var target = fixture.querySelector('#target'); - var stacks = getTextElementStack(target).map(mapToIDs); + const target = fixture.querySelector('#target'); + const stacks = getTextElementStack(target).map(mapToIDs); assert.deepEqual(stacks, [['target', '1', 'fixture']]); }); }); diff --git a/test/commons/dom/get-viewport-size.js b/test/commons/dom/get-viewport-size.js index 9bf26f373..c7e9f69da 100644 --- a/test/commons/dom/get-viewport-size.js +++ b/test/commons/dom/get-viewport-size.js @@ -1,8 +1,6 @@ -describe('dom.getViewportSize', function () { - 'use strict'; - - it('should return an object with width and height', function () { - var result = axe.commons.dom.getViewportSize(window); +describe('dom.getViewportSize', () => { + it('should return an object with width and height', () => { + const result = axe.commons.dom.getViewportSize(window); assert.property(result, 'width'); assert.property(result, 'height'); @@ -11,8 +9,8 @@ describe('dom.getViewportSize', function () { assert.isNumber(result.height); }); - it('should have some fallbacks for old browsers', function () { - var result = axe.commons.dom.getViewportSize({ + it('should have some fallbacks for old browsers', () => { + let result = axe.commons.dom.getViewportSize({ document: {}, innerWidth: 12, innerHeight: 47 diff --git a/test/commons/dom/get-visible-child-text-rects.js b/test/commons/dom/get-visible-child-text-rects.js index 844e8ea3f..016985112 100644 --- a/test/commons/dom/get-visible-child-text-rects.js +++ b/test/commons/dom/get-visible-child-text-rects.js @@ -1,4 +1,5 @@ describe('dom.getVisibleChildTextRects', () => { + const html = axe.testUtils.html; const { getVisibleChildTextRects } = axe.commons.dom; const { fixtureSetup } = axe.testUtils; const fixture = document.querySelector('#fixture'); @@ -32,7 +33,7 @@ describe('dom.getVisibleChildTextRects', () => { } it('returns the text rect of a node', () => { - fixtureSetup(`Hello`); + fixtureSetup(html`Hello`); const node = fixture.firstChild; const actual = getVisibleChildTextRects(node); @@ -40,7 +41,7 @@ describe('dom.getVisibleChildTextRects', () => { }); it('returns multiple text rects and filters out newlines', () => { - fixtureSetup(`Hello
World
`); + fixtureSetup(html`Hello
World
`); const node = fixture.firstChild; const actual = getVisibleChildTextRects(node); @@ -48,7 +49,7 @@ describe('dom.getVisibleChildTextRects', () => { }); it('returns the nodes bounding box if text rects escape bounds of node', () => { - fixtureSetup(`
Hello World
`); + fixtureSetup(html`
Hello World
`); const node = fixture.firstChild; const actual = getVisibleChildTextRects(node); @@ -56,7 +57,7 @@ describe('dom.getVisibleChildTextRects', () => { }); it('changes rect size based on overflow of parent', () => { - fixtureSetup(` + fixtureSetup(html`
Hello
@@ -70,7 +71,7 @@ describe('dom.getVisibleChildTextRects', () => { }); it('changes rect size based on overflow of all ancestors', () => { - fixtureSetup(` + fixtureSetup(html`
Hello @@ -86,9 +87,9 @@ describe('dom.getVisibleChildTextRects', () => { }); it('changes only the rect size of text rects that go outside ancestor overflow', () => { - fixtureSetup(` + fixtureSetup(html`
- Hello
World
+ Hello
World
`); const node = fixture.querySelector('#target'); @@ -108,10 +109,10 @@ describe('dom.getVisibleChildTextRects', () => { }); it('does not return rects outside overflows', () => { - fixtureSetup(` + fixtureSetup(html`
- Hello
World
Goodbye
World
+ Hello
World
Goodbye
World
`); @@ -122,7 +123,7 @@ describe('dom.getVisibleChildTextRects', () => { }); it('changes nodeRect size if all text rects got outside ancestor overflow', () => { - fixtureSetup(` + fixtureSetup(html`
Hello World
diff --git a/test/commons/dom/has-content-virtual.js b/test/commons/dom/has-content-virtual.js index 550cade8f..82e5bfdc3 100644 --- a/test/commons/dom/has-content-virtual.js +++ b/test/commons/dom/has-content-virtual.js @@ -1,12 +1,11 @@ /* global xit */ -describe('dom.hasContentVirtual', function () { - 'use strict'; - var hasContentVirtual = axe.commons.dom.hasContentVirtual; - var fixture = document.getElementById('fixture'); - var shadowSupport = axe.testUtils.shadowSupport.v1; - var tree; - - it('returns false if there is no content', function () { +describe('dom.hasContentVirtual', () => { + const hasContentVirtual = axe.commons.dom.hasContentVirtual; + const fixture = document.getElementById('fixture'); + const shadowSupport = axe.testUtils.shadowSupport.v1; + let tree; + + it('returns false if there is no content', () => { fixture.innerHTML = '
'; tree = axe.utils.getFlattenedTree(fixture); assert.isFalse( @@ -14,7 +13,7 @@ describe('dom.hasContentVirtual', function () { ); }); - it('returns false if there are non-visual elements', function () { + it('returns false if there are non-visual elements', () => { fixture.innerHTML = '
'; tree = axe.utils.getFlattenedTree(fixture); assert.isFalse( @@ -22,7 +21,7 @@ describe('dom.hasContentVirtual', function () { ); }); - it('is true if the element has non-empty text', function () { + it('is true if the element has non-empty text', () => { fixture.innerHTML = '
text
'; tree = axe.utils.getFlattenedTree(fixture); assert.isTrue( @@ -30,7 +29,7 @@ describe('dom.hasContentVirtual', function () { ); }); - it('is true if the element has an aria label', function () { + it('is true if the element has an aria label', () => { fixture.innerHTML = '
'; tree = axe.utils.getFlattenedTree(fixture); assert.isTrue( @@ -38,7 +37,7 @@ describe('dom.hasContentVirtual', function () { ); }); - it('is false if the element has an aria label but `ignoreAria=true`', function () { + it('is false if the element has an aria label but `ignoreAria=true`', () => { fixture.innerHTML = '
'; tree = axe.utils.getFlattenedTree(fixture); assert.isFalse( @@ -50,7 +49,7 @@ describe('dom.hasContentVirtual', function () { ); }); - it('is true if the element contains visual content', function () { + it('is true if the element contains visual content', () => { fixture.innerHTML = '
'; tree = axe.utils.getFlattenedTree(fixture); assert.isTrue( @@ -58,7 +57,7 @@ describe('dom.hasContentVirtual', function () { ); }); - it('is true if the element contains a node with a aria-label', function () { + it('is true if the element contains a node with a aria-label', () => { fixture.innerHTML = '
'; tree = axe.utils.getFlattenedTree(fixture); @@ -67,7 +66,7 @@ describe('dom.hasContentVirtual', function () { ); }); - it('is false if the element does not show text', function () { + it('is false if the element does not show text', () => { fixture.innerHTML = ''; tree = axe.utils.getFlattenedTree(fixture); assert.isFalse( @@ -75,8 +74,8 @@ describe('dom.hasContentVirtual', function () { ); }); - it('is called through hasContent, with a DOM node', function () { - var hasContent = axe.commons.dom.hasContent; + it('is called through hasContent, with a DOM node', () => { + const hasContent = axe.commons.dom.hasContent; fixture.innerHTML = '
text
'; axe.testUtils.flatTreeSetup(fixture); assert.isTrue(hasContent(fixture.querySelector('#target'))); @@ -86,7 +85,7 @@ describe('dom.hasContentVirtual', function () { assert.isFalse(hasContent(fixture.querySelector('#target'))); }); - it('is false if noRecursion is true and the content is not in a child', function () { + it('is false if noRecursion is true and the content is not in a child', () => { fixture.innerHTML = '
text
'; tree = axe.utils.getFlattenedTree(fixture); @@ -95,35 +94,29 @@ describe('dom.hasContentVirtual', function () { ); }); - (shadowSupport ? it : xit)( - 'looks at content of shadow dom elements', - function () { - fixture.innerHTML = '
'; - var shadow = fixture - .querySelector('#target') - .attachShadow({ mode: 'open' }); - shadow.innerHTML = 'Some text'; - tree = axe.utils.getFlattenedTree(fixture); - - assert.isTrue( - hasContentVirtual(axe.utils.querySelectorAll(tree, '#target')[0]) - ); - } - ); - - (shadowSupport ? it : xit)( - 'looks at the slots in a shadow tree', - function () { - fixture.innerHTML = '
some text
'; - var shadow = fixture - .querySelector('#shadow') - .attachShadow({ mode: 'open' }); - shadow.innerHTML = '
'; - tree = axe.utils.getFlattenedTree(fixture); - - assert.isTrue( - hasContentVirtual(axe.utils.querySelectorAll(tree, '.target')[0]) - ); - } - ); + (shadowSupport ? it : xit)('looks at content of shadow dom elements', () => { + fixture.innerHTML = '
'; + const shadow = fixture + .querySelector('#target') + .attachShadow({ mode: 'open' }); + shadow.innerHTML = 'Some text'; + tree = axe.utils.getFlattenedTree(fixture); + + assert.isTrue( + hasContentVirtual(axe.utils.querySelectorAll(tree, '#target')[0]) + ); + }); + + (shadowSupport ? it : xit)('looks at the slots in a shadow tree', () => { + fixture.innerHTML = '
some text
'; + const shadow = fixture + .querySelector('#shadow') + .attachShadow({ mode: 'open' }); + shadow.innerHTML = '
'; + tree = axe.utils.getFlattenedTree(fixture); + + assert.isTrue( + hasContentVirtual(axe.utils.querySelectorAll(tree, '.target')[0]) + ); + }); }); diff --git a/test/commons/dom/has-lang-text.js b/test/commons/dom/has-lang-text.js index a2221b2bc..2313e77df 100644 --- a/test/commons/dom/has-lang-text.js +++ b/test/commons/dom/has-lang-text.js @@ -1,101 +1,102 @@ -describe('dom.hasLangText', function () { - 'use strict'; - var hasLangText = axe.commons.dom.hasLangText; - var fixture = document.getElementById('fixture'); - var tree; +describe('dom.hasLangText', () => { + const html = axe.testUtils.html; + const hasLangText = axe.commons.dom.hasLangText; + const fixture = document.getElementById('fixture'); + let tree; - it('returns true when the element has a non-empty text node as its content', function () { + it('returns true when the element has a non-empty text node as its content', () => { fixture.innerHTML = '
text
'; tree = axe.utils.getFlattenedTree(fixture); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isTrue(hasLangText(target)); }); - it('returns true when the element has nested text node as its content', function () { + it('returns true when the element has nested text node as its content', () => { fixture.innerHTML = '
text
'; tree = axe.utils.getFlattenedTree(fixture); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isTrue(hasLangText(target)); }); - it('returns false when the element has nested text is hidden', function () { + it('returns false when the element has nested text is hidden', () => { fixture.innerHTML = '
'; tree = axe.utils.getFlattenedTree(fixture); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isFalse(hasLangText(target)); }); - it('returns true when the element has nested text is aria-hidden', function () { + it('returns true when the element has nested text is aria-hidden', () => { fixture.innerHTML = '
'; tree = axe.utils.getFlattenedTree(fixture); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isTrue(hasLangText(target)); }); - it('returns true when the element has nested text is off screen', function () { - fixture.innerHTML = - '
' + - ' text ' + - '
'; + it('returns true when the element has nested text is off screen', () => { + fixture.innerHTML = html` +
+ text +
+ `; tree = axe.utils.getFlattenedTree(fixture); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isTrue(hasLangText(target)); }); - it('returns false when the element has an empty text node as its content', function () { + it('returns false when the element has an empty text node as its content', () => { fixture.innerHTML = '
'; tree = axe.utils.getFlattenedTree(fixture); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isFalse(hasLangText(target)); }); - it('returns false if all text is in a child with a lang attribute', function () { + it('returns false if all text is in a child with a lang attribute', () => { fixture.innerHTML = '
text
'; tree = axe.utils.getFlattenedTree(fixture); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isFalse(hasLangText(target)); }); - it('does not skip if lang is on the starting node', function () { + it('does not skip if lang is on the starting node', () => { fixture.innerHTML = '
text
'; tree = axe.utils.getFlattenedTree(fixture); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isTrue(hasLangText(target)); }); - it('ignores empty lang attributes', function () { + it('ignores empty lang attributes', () => { fixture.innerHTML = '
text
'; tree = axe.utils.getFlattenedTree(fixture); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isTrue(hasLangText(target)); }); - it('ignores null lang attributes', function () { + it('ignores null lang attributes', () => { fixture.innerHTML = '
text
'; tree = axe.utils.getFlattenedTree(fixture); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isTrue(hasLangText(target)); }); - it('true for non-text content with an accessible name', function () { + it('true for non-text content with an accessible name', () => { fixture.innerHTML = '
foo
'; tree = axe.utils.getFlattenedTree(fixture); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isTrue(hasLangText(target)); }); - it('false for non-text content without accessible name', function () { + it('false for non-text content without accessible name', () => { fixture.innerHTML = '
'; tree = axe.utils.getFlattenedTree(fixture); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isFalse(hasLangText(target)); }); - it('returns false for non-text content with a lang attr', function () { + it('returns false for non-text content with a lang attr', () => { fixture.innerHTML = '
foo
'; tree = axe.utils.getFlattenedTree(fixture); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isFalse(hasLangText(target)); }); }); diff --git a/test/commons/dom/idrefs.js b/test/commons/dom/idrefs.js index b18761223..7ceacd8c4 100644 --- a/test/commons/dom/idrefs.js +++ b/test/commons/dom/idrefs.js @@ -1,36 +1,35 @@ function createContentIDR() { - 'use strict'; - var group = document.createElement('div'); + const group = document.createElement('div'); group.id = 'target'; return group; } function makeShadowTreeIDR(node) { - 'use strict'; - var root = node.attachShadow({ mode: 'open' }); - var div = document.createElement('div'); + const root = node.attachShadow({ mode: 'open' }); + const div = document.createElement('div'); div.className = 'parent'; div.setAttribute('target', 'target'); root.appendChild(div); div.appendChild(createContentIDR()); } -describe('dom.idrefs', function () { - 'use strict'; +describe('dom.idrefs', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var shadowSupported = axe.testUtils.shadowSupport.v1; + const fixture = document.getElementById('fixture'); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should find referenced nodes by ID', function () { - fixture.innerHTML = - '
' + - '
'; + it('should find referenced nodes by ID', () => { + fixture.innerHTML = html` +
+
+
+ `; - var start = document.getElementById('start'), + const start = document.getElementById('start'), expected = [ document.getElementById('target1'), document.getElementById('target2') @@ -43,49 +42,45 @@ describe('dom.idrefs', function () { ); }); - (shadowSupported ? it : xit)( - 'should find only referenced nodes within the current root: shadow DOM', - function () { - // shadow DOM v1 - note: v0 is compatible with this code, so no need - // to specifically test this - fixture.innerHTML = '
'; - makeShadowTreeIDR(fixture.firstChild); - var start = fixture.firstChild.shadowRoot.querySelector('.parent'); - var expected = [fixture.firstChild.shadowRoot.getElementById('target')]; - - assert.deepEqual( - axe.commons.dom.idrefs(start, 'target'), - expected, - 'should only find stuff in the shadow DOM' - ); - } - ); - - (shadowSupported ? it : xit)( - 'should find only referenced nodes within the current root: document', - function () { - // shadow DOM v1 - note: v0 is compatible with this code, so no need - // to specifically test this - fixture.innerHTML = - '
'; - makeShadowTreeIDR(fixture.firstChild); - var start = fixture.querySelector('.parent'); - var expected = [document.getElementById('target')]; - - assert.deepEqual( - axe.commons.dom.idrefs(start, 'target'), - expected, - 'should only find stuff in the document' - ); - } - ); - - it('should insert null if a reference is not found', function () { + it('should find only referenced nodes within the current root: shadow DOM', () => { + // shadow DOM v1 - note: v0 is compatible with this code, so no need + // to specifically test this + fixture.innerHTML = '
'; + makeShadowTreeIDR(fixture.firstChild); + const start = fixture.firstChild.shadowRoot.querySelector('.parent'); + const expected = [fixture.firstChild.shadowRoot.getElementById('target')]; + + assert.deepEqual( + axe.commons.dom.idrefs(start, 'target'), + expected, + 'should only find stuff in the shadow DOM' + ); + }); + + it('should find only referenced nodes within the current root: document', () => { + // shadow DOM v1 - note: v0 is compatible with this code, so no need + // to specifically test this fixture.innerHTML = - '
' + - '
'; + '
'; + makeShadowTreeIDR(fixture.firstChild); + const start = fixture.querySelector('.parent'); + const expected = [document.getElementById('target')]; - var start = document.getElementById('start'), + assert.deepEqual( + axe.commons.dom.idrefs(start, 'target'), + expected, + 'should only find stuff in the document' + ); + }); + + it('should insert null if a reference is not found', () => { + fixture.innerHTML = html` +
+
+
+ `; + + const start = document.getElementById('start'), expected = [ document.getElementById('target1'), document.getElementById('target2'), @@ -99,12 +94,19 @@ describe('dom.idrefs', function () { ); }); - it('should not fail when extra whitespace is used', function () { - fixture.innerHTML = - '
' + - '
'; - - var start = document.getElementById('start'), + it('should not fail when extra whitespace is used', () => { + fixture.innerHTML = html` +
+
+
+ `; + + const start = document.getElementById('start'), expected = [ document.getElementById('target1'), document.getElementById('target2'), diff --git a/test/commons/dom/inserted-into-focus-order.js b/test/commons/dom/inserted-into-focus-order.js index e3e06186f..9b7aedcc7 100644 --- a/test/commons/dom/inserted-into-focus-order.js +++ b/test/commons/dom/inserted-into-focus-order.js @@ -4,20 +4,22 @@ describe('dom.insertedIntoFocusOrder', () => { const insertedIntoFocusOrder = axe.commons.dom.insertedIntoFocusOrder; function hideByClipping(el) { - el.style.cssText = - 'position: absolute !important;' + - ' clip: rect(0px 0px 0px 0px); /* IE6, IE7 */' + - ' clip: rect(0px, 0px, 0px, 0px);'; + el.style.cssText = ` + position: absolute !important; + clip: rect(0px 0px 0px 0px); /* IE6, IE7 */ + clip: rect(0px, 0px, 0px, 0px); + `; } function hideByMovingOffScreen(el) { - el.style.cssText = - 'position:absolute;' + - ' left:-10000px;' + - ' top:auto;' + - ' width:1px;' + - ' height:1px;' + - ' overflow:hidden;'; + el.style.cssText = ` + position:absolute; + left:-10000px; + top:auto; + width:1px; + height:1px; + overflow:hidden; + `; } it('should return true for span with tabindex 0', () => { diff --git a/test/commons/dom/is-current-page-link.js b/test/commons/dom/is-current-page-link.js index 1ba00abfa..46af70c5e 100644 --- a/test/commons/dom/is-current-page-link.js +++ b/test/commons/dom/is-current-page-link.js @@ -1,65 +1,65 @@ -describe('is-current-page-link', function () { - var isCurrentPageLink = axe.commons.dom.isCurrentPageLink; - var currentPage = window.location.origin + window.location.pathname; - var base; +describe('is-current-page-link', () => { + const isCurrentPageLink = axe.commons.dom.isCurrentPageLink; + const currentPage = window.location.origin + window.location.pathname; + let base; - afterEach(function () { + afterEach(() => { if (base) { document.head.removeChild(base); } }); - it('should return true for hash links', function () { - var anchor = document.createElement('a'); + it('should return true for hash links', () => { + const anchor = document.createElement('a'); anchor.href = '#main'; document.body.appendChild(anchor); assert.isTrue(isCurrentPageLink(anchor)); }); - it('should return true for relative links to the same page', function () { - var anchor = document.createElement('a'); + it('should return true for relative links to the same page', () => { + const anchor = document.createElement('a'); anchor.href = window.location.pathname; assert.isTrue(isCurrentPageLink(anchor)); }); - it('should return true for absolute links to the same page', function () { - var anchor = document.createElement('a'); + it('should return true for absolute links to the same page', () => { + const anchor = document.createElement('a'); anchor.href = currentPage; assert.isTrue(isCurrentPageLink(anchor)); }); - it('should return true for angular skip links', function () { - var anchor = document.createElement('a'); + it('should return true for angular skip links', () => { + const anchor = document.createElement('a'); anchor.href = '/#main'; assert.isTrue(isCurrentPageLink(anchor)); }); - it('should return false for just "#"', function () { - var anchor = document.createElement('a'); + it('should return false for just "#"', () => { + const anchor = document.createElement('a'); anchor.href = '#'; assert.isFalse(isCurrentPageLink(anchor)); }); - it('should return false for relative links to a different page', function () { - var anchor = document.createElement('a'); + it('should return false for relative links to a different page', () => { + const anchor = document.createElement('a'); anchor.href = '/foo/bar/index.html'; assert.isFalse(isCurrentPageLink(anchor)); }); - it('should return false for absolute links to a different page', function () { - var anchor = document.createElement('a'); + it('should return false for absolute links to a different page', () => { + const anchor = document.createElement('a'); anchor.href = 'https://my-page.com/foo/bar/index.html'; assert.isFalse(isCurrentPageLink(anchor)); }); - it('should return false for angular router links (#!)', function () { - var anchor = document.createElement('a'); + it('should return false for angular router links (#!)', () => { + const anchor = document.createElement('a'); anchor.href = '#!main'; assert.isFalse(isCurrentPageLink(anchor)); }); - it('should return false for angular router links (#/)', function () { - var anchor = document.createElement('a'); + it('should return false for angular router links (#/)', () => { + const anchor = document.createElement('a'); anchor.href = '#/main'; assert.isFalse(isCurrentPageLink(anchor)); }); diff --git a/test/commons/dom/is-fixed-position.js b/test/commons/dom/is-fixed-position.js new file mode 100644 index 000000000..441c1ebc5 --- /dev/null +++ b/test/commons/dom/is-fixed-position.js @@ -0,0 +1,105 @@ +describe('dom.isFixedPosition', () => { + const html = axe.testUtils.html; + const isFixedPosition = axe.commons.dom.isFixedPosition; + const { queryFixture, queryShadowFixture } = axe.testUtils; + + it('returns true for element with "position: fixed"', () => { + const vNode = queryFixture( + '
' + ); + + assert.isTrue(isFixedPosition(vNode)); + }); + + it('returns false for element without position', () => { + const vNode = queryFixture('
'); + + assert.isFalse(isFixedPosition(vNode)); + }); + + for (const position of ['relative', 'absolute', 'sticky']) { + it(`returns false for element with "position: ${position}"`, () => { + const vNode = queryFixture( + html`
` + ); + + assert.isFalse(isFixedPosition(vNode)); + }); + } + + it('returns true for ancestor with position: fixed', () => { + const vNode = queryFixture( + '
' + ); + + assert.isTrue(isFixedPosition(vNode)); + }); + + it('returns true for ancestor with "position: fixed" even when the element is positioned differently', () => { + const vNode = queryFixture( + '
' + ); + + assert.isTrue(isFixedPosition(vNode)); + }); + + it('returns false on detached elements', () => { + const el = document.createElement('div'); + el.innerHTML = 'I am not visible because I am detached!'; + + assert.isFalse(axe.commons.dom.isFixedPosition(el)); + }); + + describe('options.skipAncestors', () => { + it('returns false for ancestor with "position: fixed"', () => { + const vNode = queryFixture( + '
' + ); + + assert.isFalse(isFixedPosition(vNode, { skipAncestors: true })); + }); + }); + + describe('Shadow DOM', () => { + it('returns false when no element in the composed tree has position: fixed', () => { + const vNode = queryShadowFixture( + '
', + '' + ); + assert.isFalse(isFixedPosition(vNode)); + }); + + it('returns true for element in shadow tree with position: fixed', () => { + const vNode = queryShadowFixture( + '
', + '
' + ); + + assert.isTrue(isFixedPosition(vNode)); + }); + + it('returns true when ancestor outside shadow tree has position: fixed', () => { + const vNode = queryShadowFixture( + '
', + '' + ); + assert.isTrue(isFixedPosition(vNode)); + }); + + it('returns true when ancestor outside shadow is fixed and target in shadow has a different position', () => { + const vNode = queryShadowFixture( + '
', + '' + ); + assert.isTrue(isFixedPosition(vNode)); + }); + + it('returns false with skipAncestors when only ancestor outside shadow tree is fixed', () => { + const vNode = queryShadowFixture( + '
', + '' + ); + assert.isFalse(isFixedPosition(vNode, { skipAncestors: true })); + }); + }); +}); diff --git a/test/commons/dom/is-hidden-for-everyone.js b/test/commons/dom/is-hidden-for-everyone.js index 42d14c6e0..506bb2603 100644 --- a/test/commons/dom/is-hidden-for-everyone.js +++ b/test/commons/dom/is-hidden-for-everyone.js @@ -1,20 +1,14 @@ describe('dom.isHiddenForEveryone', () => { - 'use strict'; + const html = axe.testUtils.html; const fixture = document.getElementById('fixture'); - const shadowSupported = axe.testUtils.shadowSupport.v1; const isHiddenForEveryone = axe.commons.dom.isHiddenForEveryone; const queryFixture = axe.testUtils.queryFixture; const contentVisibilitySupported = CSS.supports('content-visibility: hidden'); function createContentSlotted(mainProps, targetProps) { const group = document.createElement('div'); - group.innerHTML = - '

'; + group.innerHTML = `

`; return group; } @@ -131,25 +125,25 @@ describe('dom.isHiddenForEveryone', () => { }); it('should return true for if parent of element set to `display:none`', () => { - const vNode = queryFixture( - '
' + - '
' + - '

I am not visible

' + - '
' + - '
' - ); + const vNode = queryFixture(html` +
+
+

I am not visible

+
+
+ `); const actual = isHiddenForEveryone(vNode); assert.isTrue(actual); }); it('should return false for if parent of element set to `display:block`', () => { - const vNode = queryFixture( - '
' + - '
' + - '

I am visible

' + - '
' + - '
' - ); + const vNode = queryFixture(html` +
+
+

I am visible

+
+
+ `); const actual = isHiddenForEveryone(vNode); assert.isFalse(actual); }); @@ -164,93 +158,93 @@ describe('dom.isHiddenForEveryone', () => { }); it('should return false and compute how `visibility` of self and parent is configured', () => { - const vNode = queryFixture( - '
' + - '
' + - '
I am visible
' + - '
' + - '
' - ); + const vNode = queryFixture(html` +
+
+
I am visible
+
+
+ `); const actual = isHiddenForEveryone(vNode); assert.isFalse(actual); }); it('should return false and compute how `visibility` of self and parent is configured', () => { - const vNode = queryFixture( - '
' + - '
' + - '
I am visible
' + - '
' + - '
' - ); + const vNode = queryFixture(html` +
+
+
I am visible
+
+
+ `); const actual = isHiddenForEveryone(vNode); assert.isFalse(actual); }); it('should return true and as parent is set to `visibility:hidden`', () => { - const vNode = queryFixture( - '
' + - '
' + - '
I am not visible
' + - '
' + - '
' - ); + const vNode = queryFixture(html` +
+
+
I am not visible
+
+
+ `); const actual = isHiddenForEveryone(vNode); assert.isTrue(actual); }); // mixing display and visibility it('should return true and compute using both `display` and `visibility` set on element and parent(s)', () => { - const vNode = queryFixture( - '
' + - '
' + - '
I am not visible
' + - '
' + - '
' - ); + const vNode = queryFixture(html` +
+
+
I am not visible
+
+
+ `); const actual = isHiddenForEveryone(vNode); assert.isTrue(actual); }); it('should return false and compute using both `display` and `visibility` set on element and parent(s)', () => { - const vNode = queryFixture( - '
' + - '
' + - '
I am visible
' + - '
' + - '
' - ); + const vNode = queryFixture(html` +
+
+
I am visible
+
+
+ `); const actual = isHiddenForEveryone(vNode); assert.isFalse(actual); }); it('should return true and compute using both `display` and `visibility` set on element and parent(s)', () => { - const vNode = queryFixture( - '
' + - '
' + - '' + - '
' + - '
' - ); + const vNode = queryFixture(html` +
+
+ +
+
+ `); const actual = isHiddenForEveryone(vNode); assert.isTrue(actual); }); it('should return true and compute using both `display` and `visibility` set on element and parent(s)', () => { - const vNode = queryFixture( - '
' + - '
' + - '
I am not visible
' + - '
' + - '
' - ); + const vNode = queryFixture(html` +
+
+
I am not visible
+
+
+ `); const actual = isHiddenForEveryone(vNode); assert.isTrue(actual); }); describe('details', () => { it('should return true for element in closed details', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World

Hidden

@@ -261,7 +255,7 @@ describe('dom.isHiddenForEveryone', () => { }); it('should return false for closed details', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World

Hidden

@@ -272,7 +266,7 @@ describe('dom.isHiddenForEveryone', () => { }); it('should return false for summary element closed details', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World

Hidden

@@ -283,7 +277,7 @@ describe('dom.isHiddenForEveryone', () => { }); it('should return false for element in open details', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World

Hidden

@@ -294,7 +288,7 @@ describe('dom.isHiddenForEveryone', () => { }); it('should return true for grandchild element in closed details', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World

Hidden

@@ -305,7 +299,7 @@ describe('dom.isHiddenForEveryone', () => { }); it('should return true for grandchild summary in close details', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World

Hidden

@@ -316,7 +310,7 @@ describe('dom.isHiddenForEveryone', () => { }); it('should return true for not first summary in close details', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World Not summary @@ -328,45 +322,36 @@ describe('dom.isHiddenForEveryone', () => { }); }); - (shadowSupported ? it : it.skip)( - 'should return true if `display:none` inside shadowDOM', - () => { - fixture.innerHTML = '
'; - makeShadowTree(fixture.firstChild, 'display:none;', ''); - const tree = axe.utils.getFlattenedTree(fixture.firstChild); - const vNode = axe.utils.querySelectorAll(tree, 'p')[0]; - const actual = isHiddenForEveryone(vNode); - assert.isTrue(actual); - } - ); + it('should return true if `display:none` inside shadowDOM', () => { + fixture.innerHTML = '
'; + makeShadowTree(fixture.firstChild, 'display:none;', ''); + const tree = axe.utils.getFlattenedTree(fixture.firstChild); + const vNode = axe.utils.querySelectorAll(tree, 'p')[0]; + const actual = isHiddenForEveryone(vNode); + assert.isTrue(actual); + }); - (shadowSupported ? it : xit)( - 'should return true as parent shadowDOM host is set to `visibility:hidden`', - () => { - fixture.innerHTML = '
'; - makeShadowTree(fixture.firstChild, 'visibility:hidden', ''); - const tree = axe.utils.getFlattenedTree(fixture.firstChild); - const vNode = axe.utils.querySelectorAll(tree, 'p')[0]; - const actual = isHiddenForEveryone(vNode); - assert.isTrue(actual); - } - ); + it('should return true as parent shadowDOM host is set to `visibility:hidden`', () => { + fixture.innerHTML = '
'; + makeShadowTree(fixture.firstChild, 'visibility:hidden', ''); + const tree = axe.utils.getFlattenedTree(fixture.firstChild); + const vNode = axe.utils.querySelectorAll(tree, 'p')[0]; + const actual = isHiddenForEveryone(vNode); + assert.isTrue(actual); + }); - (shadowSupported ? it : xit)( - 'should return false as parent shadowDOM host set to `visibility:hidden` is overriden', - () => { - fixture.innerHTML = '
'; - makeShadowTree( - fixture.firstChild, - 'visibility:hidden', - 'visibility:visible' - ); - const tree = axe.utils.getFlattenedTree(fixture.firstChild); - const vNode = axe.utils.querySelectorAll(tree, 'p')[0]; - const actual = isHiddenForEveryone(vNode); - assert.isFalse(actual); - } - ); + it('should return false as parent shadowDOM host set to `visibility:hidden` is overriden', () => { + fixture.innerHTML = '
'; + makeShadowTree( + fixture.firstChild, + 'visibility:hidden', + 'visibility:visible' + ); + const tree = axe.utils.getFlattenedTree(fixture.firstChild); + const vNode = axe.utils.querySelectorAll(tree, 'p')[0]; + const actual = isHiddenForEveryone(vNode); + assert.isFalse(actual); + }); (contentVisibilitySupported ? it : xit)( 'should return true for `content-visibility: hidden` parent', diff --git a/test/commons/dom/is-hidden-with-css.js b/test/commons/dom/is-hidden-with-css.js index cbf5a9099..e77a1a3e4 100644 --- a/test/commons/dom/is-hidden-with-css.js +++ b/test/commons/dom/is-hidden-with-css.js @@ -1,324 +1,321 @@ -describe('dom.isHiddenWithCSS', function () { - 'use strict'; +describe('dom.isHiddenWithCSS', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var shadowSupported = axe.testUtils.shadowSupport.v1; - var isHiddenWithCSS = axe.commons.dom.isHiddenWithCSS; - var origComputedStyle = window.getComputedStyle; - var queryFixture = axe.testUtils.queryFixture; + const fixture = document.getElementById('fixture'); + const isHiddenWithCSS = axe.commons.dom.isHiddenWithCSS; + const origComputedStyle = window.getComputedStyle; + const queryFixture = axe.testUtils.queryFixture; function createContentSlotted(mainProps, targetProps) { - var group = document.createElement('div'); - group.innerHTML = - '

'; + const group = document.createElement('div'); + group.innerHTML = `

`; return group; } function makeShadowTree(node, mainProps, targetProps) { - var root = node.attachShadow({ mode: 'open' }); - var node = createContentSlotted(mainProps, targetProps); - root.appendChild(node); + const root = node.attachShadow({ mode: 'open' }); + const content = createContentSlotted(mainProps, targetProps); + root.appendChild(content); } - afterEach(function () { + afterEach(() => { window.getComputedStyle = origComputedStyle; document.getElementById('fixture').innerHTML = ''; }); - it('should throw an error if computedStyle returns null', function () { - window.getComputedStyle = function () { + it('should throw an error if computedStyle returns null', () => { + window.getComputedStyle = () => { return null; }; - var fakeNode = { + const fakeNode = { nodeType: Node.ELEMENT_NODE, nodeName: 'div' }; - assert.throws(function () { + assert.throws(() => { isHiddenWithCSS(fakeNode); }); }); - it('should return false on static-positioned, visible element', function () { + it('should return false on static-positioned, visible element', () => { fixture.innerHTML = '
I am visible
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isFalse(actual); }); - it('should return true on static-positioned, hidden element', function () { + it('should return true on static-positioned, hidden element', () => { fixture.innerHTML = ''; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isTrue(actual); }); - it('should return false on absolutely positioned elements that are on-screen', function () { + it('should return false on absolutely positioned elements that are on-screen', () => { fixture.innerHTML = '
I am visible
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isFalse(actual); }); - it('should return false for off-screen and aria-hidden element', function () { + it('should return false for off-screen and aria-hidden element', () => { fixture.innerHTML = ''; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isFalse(actual); }); - it('should return false on fixed position elements that are on-screen', function () { + it('should return false on fixed position elements that are on-screen', () => { fixture.innerHTML = '
I am visible
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isFalse(actual); }); - it('should return false for off-screen absolutely positioned element', function () { + it('should return false for off-screen absolutely positioned element', () => { fixture.innerHTML = '
I am visible
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isFalse(actual); }); - it('should return false for off-screen fixed positioned element', function () { + it('should return false for off-screen fixed positioned element', () => { fixture.innerHTML = '
I am visible
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isFalse(actual); }); - it('should return false on detached elements', function () { - var el = document.createElement('div'); + it('should return false on detached elements', () => { + const el = document.createElement('div'); el.innerHTML = 'I am not visible because I am detached!'; - var actual = isHiddenWithCSS(el); + const actual = isHiddenWithCSS(el); assert.isFalse(actual); }); - it('should return false on a document', function () { - var actual = isHiddenWithCSS(document); + it('should return false on a document', () => { + const actual = isHiddenWithCSS(document); assert.isFalse(actual); }); - it('should return false if static-position but top/left is set', function () { + it('should return false if static-position but top/left is set', () => { fixture.innerHTML = '
I am visible
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isFalse(actual); }); - it('should return false, and not be affected by `aria-hidden`', function () { + it('should return false, and not be affected by `aria-hidden`', () => { fixture.innerHTML = ''; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isFalse(actual); }); - it('should return false for STYLE node', function () { + it('should return false for STYLE node', () => { fixture.innerHTML = ""; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isFalse(actual); }); - it('should return false for SCRIPT node', function () { + it('should return false for SCRIPT node', () => { fixture.innerHTML = ""; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isFalse(actual); }); // `display` test - it('should return true for if parent of element set to `display:none`', function () { - fixture.innerHTML = - '
' + - '
' + - '

I am not visible

' + - '
' + - '
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + it('should return true for if parent of element set to `display:none`', () => { + fixture.innerHTML = html` +
+
+

I am not visible

+
+
+ `; + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isTrue(actual); }); - it('should return true for if parent of element set to `display:none`', function () { - fixture.innerHTML = - '
' + - '
' + - '

I am not visible

' + - '
' + - '
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + it('should return true for if parent of element set to `display:none`', () => { + fixture.innerHTML = html` +
+
+

I am not visible

+
+
+ `; + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isTrue(actual); }); - it('should return false for if parent of element set to `display:block`', function () { - fixture.innerHTML = - '
' + - '
' + - '

I am visible

' + - '
' + - '
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + it('should return false for if parent of element set to `display:block`', () => { + fixture.innerHTML = html` +
+
+

I am visible

+
+
+ `; + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isFalse(actual); }); - (shadowSupported ? it : it.skip)( - 'should return true if `display:none` inside shadowDOM', - function () { - fixture.innerHTML = '
'; - makeShadowTree(fixture.firstChild, 'display:none;', ''); - var tree = axe.utils.getFlattenedTree(fixture.firstChild); - var el = axe.utils.querySelectorAll(tree, 'p')[0]; - var actual = isHiddenWithCSS(el.actualNode); - assert.isTrue(actual); - } - ); + it('should return true if `display:none` inside shadowDOM', () => { + fixture.innerHTML = '
'; + makeShadowTree(fixture.firstChild, 'display:none;', ''); + const tree = axe.utils.getFlattenedTree(fixture.firstChild); + const el = axe.utils.querySelectorAll(tree, 'p')[0]; + const actual = isHiddenWithCSS(el.actualNode); + assert.isTrue(actual); + }); // `visibility` test - it('should return true for element that has `visibility:hidden`', function () { + it('should return true for element that has `visibility:hidden`', () => { fixture.innerHTML = ''; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isTrue(actual); }); - it('should return false and compute how `visibility` of self and parent is configured', function () { - fixture.innerHTML = - '
' + - '
' + - '
I am visible
' + - '
' + - '
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + it('should return false and compute how `visibility` of self and parent is configured', () => { + fixture.innerHTML = html` +
+
+
I am visible
+
+
+ `; + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isFalse(actual); }); - it('should return false and compute how `visibility` of self and parent is configured', function () { - fixture.innerHTML = - '
' + - '
' + - '
I am visible
' + - '
' + - '
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + it('should return false and compute how `visibility` of self and parent is configured', () => { + fixture.innerHTML = html` +
+
+
I am visible
+
+
+ `; + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isFalse(actual); }); - it('should return true and as parent is set to `visibility:hidden`', function () { - fixture.innerHTML = - '
' + - '
' + - '
I am not visible
' + - '
' + - '
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + it('should return true and as parent is set to `visibility:hidden`', () => { + fixture.innerHTML = html` +
+
+
I am not visible
+
+
+ `; + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isTrue(actual); }); - (shadowSupported ? it : xit)( - 'should return true as parent shadowDOM host is set to `visibility:hidden`', - function () { - fixture.innerHTML = '
'; - makeShadowTree(fixture.firstChild, 'visibility:hidden', ''); - var tree = axe.utils.getFlattenedTree(fixture.firstChild); - var el = axe.utils.querySelectorAll(tree, 'p')[0]; - var actual = isHiddenWithCSS(el.actualNode); - assert.isTrue(actual); - } - ); - - (shadowSupported ? it : xit)( - 'should return false as parent shadowDOM host set to `visibility:hidden` is overriden', - function () { - fixture.innerHTML = '
'; - makeShadowTree( - fixture.firstChild, - 'visibility:hidden', - 'visibility:visible' - ); - var tree = axe.utils.getFlattenedTree(fixture.firstChild); - var el = axe.utils.querySelectorAll(tree, 'p')[0]; - var actual = isHiddenWithCSS(el.actualNode); - assert.isFalse(actual); - } - ); + it('should return true as parent shadowDOM host is set to `visibility:hidden`', () => { + fixture.innerHTML = '
'; + makeShadowTree(fixture.firstChild, 'visibility:hidden', ''); + const tree = axe.utils.getFlattenedTree(fixture.firstChild); + const el = axe.utils.querySelectorAll(tree, 'p')[0]; + const actual = isHiddenWithCSS(el.actualNode); + assert.isTrue(actual); + }); + + it('should return false as parent shadowDOM host set to `visibility:hidden` is overriden', () => { + fixture.innerHTML = '
'; + makeShadowTree( + fixture.firstChild, + 'visibility:hidden', + 'visibility:visible' + ); + const tree = axe.utils.getFlattenedTree(fixture.firstChild); + const el = axe.utils.querySelectorAll(tree, 'p')[0]; + const actual = isHiddenWithCSS(el.actualNode); + assert.isFalse(actual); + }); // mixing display and visibility - it('should return true and compute using both `display` and `visibility` set on element and parent(s)', function () { - fixture.innerHTML = - '
' + - '
' + - '
I am not visible
' + - '
' + - '
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + it('should return true and compute using both `display` and `visibility` set on element and parent(s)', () => { + fixture.innerHTML = html` +
+
+
I am not visible
+
+
+ `; + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isTrue(actual); }); - it('should return false and compute using both `display` and `visibility` set on element and parent(s)', function () { - fixture.innerHTML = - '
' + - '
' + - '
I am visible
' + - '
' + - '
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + it('should return false and compute using both `display` and `visibility` set on element and parent(s)', () => { + fixture.innerHTML = html` +
+
+
I am visible
+
+
+ `; + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isFalse(actual); }); - it('should return true and compute using both `display` and `visibility` set on element and parent(s)', function () { - fixture.innerHTML = - '
' + - '
' + - '' + - '
' + - '
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + it('should return true and compute using both `display` and `visibility` set on element and parent(s)', () => { + fixture.innerHTML = html` +
+
+ +
+
+ `; + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isTrue(actual); }); - it('should return true and compute using both `display` and `visibility` set on element and parent(s)', function () { - fixture.innerHTML = - '
' + - '
' + - '
I am not visible
' + - '
' + - '
'; - var el = document.getElementById('target'); - var actual = isHiddenWithCSS(el); + it('should return true and compute using both `display` and `visibility` set on element and parent(s)', () => { + fixture.innerHTML = html` +
+
+
I am not visible
+
+
+ `; + const el = document.getElementById('target'); + const actual = isHiddenWithCSS(el); assert.isTrue(actual); }); - describe('with virtual nodes', function () { - it('returns false when virtual nodes are visible', function () { - var vNode = queryFixture('
'); + describe('with virtual nodes', () => { + it('returns false when virtual nodes are visible', () => { + const vNode = queryFixture('
'); assert.isFalse(isHiddenWithCSS(vNode)); }); - it('returns true when virtual nodes are hidden', function () { - var vNode = queryFixture(''); + it('returns true when virtual nodes are hidden', () => { + const vNode = queryFixture( + '' + ); assert.isTrue(isHiddenWithCSS(vNode)); }); }); diff --git a/test/commons/dom/is-html5.js b/test/commons/dom/is-html5.js index ae9927114..ad71ebaab 100644 --- a/test/commons/dom/is-html5.js +++ b/test/commons/dom/is-html5.js @@ -1,8 +1,6 @@ -describe('dom.isHTML5', function () { - 'use strict'; - - it('should return false on any document that is not HTML5', function () { - var doc = document.implementation.createDocument( +describe('dom.isHTML5', () => { + it('should return false on any document that is not HTML5', () => { + const doc = document.implementation.createDocument( 'http://www.w3.org/1999/xhtml', 'html', null @@ -10,12 +8,12 @@ describe('dom.isHTML5', function () { assert.isFalse(axe.commons.dom.isHTML5(doc)); }); - it('should return true on any document that is HTML5', function () { - var doc = document.implementation.createHTMLDocument('Monkeys'); + it('should return true on any document that is HTML5', () => { + const doc = document.implementation.createHTMLDocument('Monkeys'); assert.isTrue(axe.commons.dom.isHTML5(doc)); }); - it('should return true on any document that is HTML5 - fixture', function () { + it('should return true on any document that is HTML5 - fixture', () => { assert.isTrue(axe.commons.dom.isHTML5(document)); }); }); diff --git a/test/commons/dom/is-in-tab-order.js b/test/commons/dom/is-in-tab-order.js index 328805e83..c9ba3fbc1 100644 --- a/test/commons/dom/is-in-tab-order.js +++ b/test/commons/dom/is-in-tab-order.js @@ -1,95 +1,93 @@ -describe('dom.isInTabOrder', function () { - 'use strict'; +describe('dom.isInTabOrder', () => { + const queryFixture = axe.testUtils.queryFixture; + const isInTabOrder = axe.commons.dom.isInTabOrder; - var queryFixture = axe.testUtils.queryFixture; - var isInTabOrder = axe.commons.dom.isInTabOrder; - - it('should return false for presentation element with negative tabindex', function () { - var target = queryFixture('
'); + it('should return false for presentation element with negative tabindex', () => { + const target = queryFixture('
'); assert.isFalse(isInTabOrder(target)); }); - it('should return true for presentation element with positive tabindex', function () { - var target = queryFixture('
'); + it('should return true for presentation element with positive tabindex', () => { + const target = queryFixture('
'); assert.isTrue(isInTabOrder(target)); }); - it('should return false for presentation element with tabindex not set', function () { - var target = queryFixture('
'); + it('should return false for presentation element with tabindex not set', () => { + const target = queryFixture('
'); assert.isFalse(isInTabOrder(target)); }); - it('should return false for presentation element with tabindex set to non-parseable value', function () { - var target = queryFixture('
'); + it('should return false for presentation element with tabindex set to non-parseable value', () => { + const target = queryFixture('
'); assert.isFalse(isInTabOrder(target)); }); - it('should return false for presentation element with tabindex not set and role of natively focusable element', function () { - var target = queryFixture('
'); + it('should return false for presentation element with tabindex not set and role of natively focusable element', () => { + const target = queryFixture('
'); assert.isFalse(isInTabOrder(target)); }); - it('should return true for natively focusable element with tabindex 0', function () { - var target = queryFixture(''); + it('should return true for natively focusable element with tabindex 0', () => { + const target = queryFixture(''); assert.isTrue(isInTabOrder(target)); }); - it('should return true for natively focusable element with tabindex 1', function () { - var target = queryFixture(''); + it('should return true for natively focusable element with tabindex 1', () => { + const target = queryFixture(''); assert.isTrue(isInTabOrder(target)); }); - it('should return false for natively focusable element with tabindex -1', function () { - var target = queryFixture(''); + it('should return false for natively focusable element with tabindex -1', () => { + const target = queryFixture(''); assert.isFalse(isInTabOrder(target)); }); - it('should return true for natively focusable element with tabindex not set', function () { - var target = queryFixture(''); + it('should return true for natively focusable element with tabindex not set', () => { + const target = queryFixture(''); assert.isTrue(isInTabOrder(target)); }); - it('should return true for natively focusable element with tabindex set to empty string', function () { - var target = queryFixture(''); + it('should return true for natively focusable element with tabindex set to empty string', () => { + const target = queryFixture(''); assert.isTrue(isInTabOrder(target)); }); - it('should return true for natively focusable element with tabindex set to non-parseable value', function () { - var target = queryFixture( + it('should return true for natively focusable element with tabindex set to non-parseable value', () => { + const target = queryFixture( '' ); assert.isTrue(isInTabOrder(target)); }); - it('should return false for disabled', function () { - var target = queryFixture(''); + it('should return false for disabled', () => { + const target = queryFixture(''); assert.isFalse(isInTabOrder(target)); }); - it('should return false for disabled natively focusable element with tabindex', function () { - var target = queryFixture( + it('should return false for disabled natively focusable element with tabindex', () => { + const target = queryFixture( '' ); assert.isFalse(isInTabOrder(target)); }); - it('should return false for hidden inputs', function () { - var target = queryFixture(''); + it('should return false for hidden inputs', () => { + const target = queryFixture(''); assert.isFalse(isInTabOrder(target)); }); - it('should return false for non-element nodes', function () { - var target = queryFixture('Hello World'); + it('should return false for non-element nodes', () => { + const target = queryFixture('Hello World'); assert.isFalse(isInTabOrder(target.children[0])); }); - it('should return false for natively focusable hidden element', function () { - var target = queryFixture(''); + it('should return false for natively focusable hidden element', () => { + const target = queryFixture(''); assert.isFalse(isInTabOrder(target)); }); - it('should return for false hidden element with tabindex 1', function () { - var target = queryFixture(''); + it('should return for false hidden element with tabindex 1', () => { + const target = queryFixture(''); assert.isFalse(isInTabOrder(target)); }); }); diff --git a/test/commons/dom/is-in-text-block.js b/test/commons/dom/is-in-text-block.js index 936669371..9e730a619 100644 --- a/test/commons/dom/is-in-text-block.js +++ b/test/commons/dom/is-in-text-block.js @@ -1,237 +1,413 @@ -describe('dom.isInTextBlock', function () { - 'use strict'; +describe('dom.isInTextBlock', () => { + const html = axe.testUtils.html; + const fixture = document.getElementById('fixture'); + const { fixtureSetup, queryFixture } = axe.testUtils; + const isInTextBlock = axe.commons.dom.isInTextBlock; - var fixture = document.getElementById('fixture'); - var shadowSupport = axe.testUtils.shadowSupport; - var fixtureSetup = axe.testUtils.fixtureSetup; + it('returns true if the element is a node in a block of text', () => { + fixtureSetup(html` +

+ Some paragraph with text + link +

+ `); + const link = document.getElementById('link'); + assert.isTrue(isInTextBlock(link)); + }); - afterEach(function () { - fixture.innerHTML = ''; + it('returns false if the element is a block', () => { + fixtureSetup(html` +

+ Some paragraph with text + link +

+ `); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link)); }); - it('returns true if the element is a node in a block of text', function () { - fixtureSetup( - '

Some paragraph with text ' + - ' link' + - '

' - ); - var link = document.getElementById('link'); - assert.isTrue(axe.commons.dom.isInTextBlock(link)); + it('returns false if the element has the only text in the block', () => { + fixtureSetup('

link

'); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link)); }); - it('returns false if the element is a block', function () { - fixtureSetup( - '

Some paragraph with text ' + - ' link' + - '

' - ); - var link = document.getElementById('link'); - assert.isFalse(axe.commons.dom.isInTextBlock(link)); + it('returns false if there is more text in link(s) than in the rest of the block', () => { + fixtureSetup(html` +

+ short text: + on a link with a very long text +

+ `); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link)); }); - it('returns false if the element has the only text in the block', function () { - fixtureSetup('

link

'); - var link = document.getElementById('link'); - assert.isFalse(axe.commons.dom.isInTextBlock(link)); - }); - - it('returns false if there is more text in link(s) than in the rest of the block', function () { - fixtureSetup( - '

short text:' + - ' on a link with a very long text' + - '

' - ); - var link = document.getElementById('link'); - assert.isFalse(axe.commons.dom.isInTextBlock(link)); - }); - - it('return false if there are links along side other links', function () { - fixtureSetup( - '

' + - ' link' + - ' other link' + - '

' - ); - var link = document.getElementById('link'); - assert.isFalse(axe.commons.dom.isInTextBlock(link)); - }); - - it('ignores hidden content', function () { - fixtureSetup( - '

' + - ' link' + - ' some hidden text' + - '

' - ); - var link = document.getElementById('link'); - assert.isFalse(axe.commons.dom.isInTextBlock(link)); - }); - - it('ignores floated content', function () { - fixtureSetup( - '

' + - ' A floating text in the area' + - ' link' + - '

' - ); - var link = document.getElementById('link'); - assert.isFalse(axe.commons.dom.isInTextBlock(link)); - }); - - it('ignores positioned content', function () { - fixtureSetup( - '

' + - ' Some absolute potitioned text' + - ' link' + - '

' - ); - var link = document.getElementById('link'); - assert.isFalse(axe.commons.dom.isInTextBlock(link)); - }); - - it('ignores none-text content', function () { - fixtureSetup( - '

' + - ' Some graphical component' + - ' link' + - '

' - ); - var link = document.getElementById('link'); - assert.isFalse(axe.commons.dom.isInTextBlock(link)); - }); - - it('ignore text in the block coming before a br', function () { - fixtureSetup( - '

Some paragraph with text
' + - ' link' + - '

' - ); - var link = document.getElementById('link'); - assert.isFalse(axe.commons.dom.isInTextBlock(link)); - }); - - it('ignore text in the block coming after a br', function () { - fixtureSetup( - '

' + - ' link
' + - ' Some paragraph with text ' + - '

' - ); - var link = document.getElementById('link'); - assert.isFalse(axe.commons.dom.isInTextBlock(link)); - }); - - it('ignore text in the block coming before and after a br', function () { - fixtureSetup( - '

Some paragraph with text
' + - ' link
' + - ' Some paragraph with text ' + - '

' - ); - var link = document.getElementById('link'); - assert.isFalse(axe.commons.dom.isInTextBlock(link)); - }); - - it('ignores text inside inline widgets and components', function () { - fixtureSetup( - '

' + - ' link' + - ' ' + - ' ' + - ' My query' + - ' ' + - ' ' + - '

' - ); - var link = document.getElementById('link'); - assert.isFalse(axe.commons.dom.isInTextBlock(link)); - }); - - it('treats hr elements the same as br elements', function () { - fixtureSetup( - '
Some paragraph with text
' + - ' link
' + - ' Some paragraph with text ' + - '
' - ); - var link = document.getElementById('link'); - assert.isFalse(axe.commons.dom.isInTextBlock(link)); - }); - - it('ignore comments', function () { - fixtureSetup( - '

' + - ' link' + - '

' - ); - var link = document.getElementById('link'); - assert.isFalse(axe.commons.dom.isInTextBlock(link)); - }); - - (shadowSupport.v1 ? it : xit)('can reach outside a shadow tree', function () { - var div = document.createElement('div'); + it('return false if there are links along side other links', () => { + fixtureSetup(html` +

+ link + other link +

+ `); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link)); + }); + + it('ignores hidden content', () => { + fixtureSetup(html` +

+ link + some hidden text +

+ `); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link)); + }); + + it('ignores floated content', () => { + fixtureSetup(html` +

+ A floating text in the area + link +

+ `); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link)); + }); + + it('ignores positioned content', () => { + fixtureSetup(html` +

+ Some absolute potitioned text + link +

+ `); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link)); + }); + + it('ignores none-text content', () => { + fixtureSetup(html` +

+ Some graphical component + link +

+ `); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link)); + }); + + it('ignore text in the block coming before a br', () => { + fixtureSetup(html` +

+ Some paragraph with text
+ link +

+ `); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link)); + }); + + it('ignore text in the block coming after a br', () => { + fixtureSetup(html` +

+ link
+ Some paragraph with text +

+ `); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link)); + }); + + it('ignore text in the block coming before and after a br', () => { + fixtureSetup(html` +

+ Some paragraph with text
+ link
+ Some paragraph with text +

+ `); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link)); + }); + + it('ignores text inside inline widgets and components', () => { + fixtureSetup(html` +

+ link + + + My query + + +

+ `); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link)); + }); + + it('treats hr elements the same as br elements', () => { + fixtureSetup(html` +
+ Some paragraph with text +
+ link +
+ Some paragraph with text +
+ `); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link)); + }); + + it('ignore comments', () => { + fixtureSetup(html` +

+ + link +

+ `); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link)); + }); + + it('can reach outside a shadow tree', () => { + const div = document.createElement('div'); div.innerHTML = 'Some paragraph with text '; - var shadow = div.querySelector('span').attachShadow({ mode: 'open' }); + const shadow = div.querySelector('span').attachShadow({ mode: 'open' }); shadow.innerHTML = 'link'; fixtureSetup(div); - var link = shadow.querySelector('#link'); - assert.isTrue(axe.commons.dom.isInTextBlock(link)); + const link = shadow.querySelector('#link'); + assert.isTrue(isInTextBlock(link)); }); - (shadowSupport.v1 ? it : xit)('can reach into a shadow tree', function () { - var div = document.createElement('div'); + it('can reach into a shadow tree', () => { + const div = document.createElement('div'); div.innerHTML = 'link'; - var shadow = div.attachShadow({ mode: 'open' }); + const shadow = div.attachShadow({ mode: 'open' }); shadow.innerHTML = '

Some paragraph with text

'; fixtureSetup(div); - var link = fixture.querySelector('#link'); - assert.isTrue(axe.commons.dom.isInTextBlock(link)); + const link = fixture.querySelector('#link'); + assert.isTrue(isInTextBlock(link)); }); - (shadowSupport.v1 ? it : xit)( - 'treats shadow DOM slots as siblings', - function () { - var div = document.createElement('div'); - div.innerHTML = '
'; - var shadow = div.attachShadow({ mode: 'open' }); - shadow.innerHTML = - '

Some paragraph with text ' + - ' link

'; - fixtureSetup(div); + it('treats shadow DOM slots as siblings', () => { + const div = document.createElement('div'); + div.innerHTML = '
'; + const shadow = div.attachShadow({ mode: 'open' }); + shadow.innerHTML = html` +

+ Some paragraph with text link +

+ `; + fixtureSetup(div); + + const link = shadow.querySelector('#link'); + assert.isFalse(isInTextBlock(link)); + }); + + describe('inline-block element', () => { + it('returns false if element has widget parent', () => { + const target = queryFixture(html` +
+ +
+ `); + assert.isFalse(isInTextBlock(target)); + }); + + it('returns false if element has focusable parent', () => { + const target = queryFixture(html` +
+ +
+ `); + assert.isFalse(isInTextBlock(target)); + }); + + it('returns false if element has text sibling', () => { + const target = queryFixture(html` +
+ Hello world + +
+ `); + assert.isFalse(isInTextBlock(target)); + }); + + it('returns false if element has inline element sibling', () => { + const target = queryFixture(html` +
+ Hello world + +
+ `); + assert.isFalse(isInTextBlock(target)); + }); + + it('returns false if element has inline-block element sibling', () => { + const target = queryFixture(html` +
+
Hello world
+ +
+ `); + assert.isFalse(isInTextBlock(target)); + }); + + it('returns false if element has widget element sibling', () => { + const target = queryFixture(html` +
+
Hello world
+ +
+ `); + assert.isFalse(isInTextBlock(target)); + }); + + it('ignores br elements', () => { + const target = queryFixture(html` +
+ Hello World +
+ +
+ `); + assert.isFalse(isInTextBlock(target)); + }); - var link = shadow.querySelector('#link'); - assert.isFalse(axe.commons.dom.isInTextBlock(link)); - } - ); + it('ignores hr elements', () => { + const target = queryFixture(html` +
+ Hello World +
+ +
+ `); + assert.isFalse(isInTextBlock(target)); + }); + + it('looks at all siblings', () => { + const target = queryFixture(html` +
+
+ +
+
Hello world
+
+ `); + assert.isFalse(isInTextBlock(target)); + }); + + it('works for inline-grid', () => { + const target = queryFixture(html` +
+
Hello world
+ +
+ `); + assert.isFalse(isInTextBlock(target)); + }); + + it('works for inline-flex', () => { + const target = queryFixture(html` +
+
Hello world
+ +
+ `); + assert.isFalse(isInTextBlock(target)); + }); + }); - describe('options.noLengthCompare', function () { - it('returns true if there is any text outside the link', function () { + describe('options.noLengthCompare', () => { + it('returns true if there is any text outside the link', () => { fixtureSetup('

amy link text is longer

'); - var link = document.getElementById('link'); - assert.isTrue( - axe.commons.dom.isInTextBlock(link, { - noLengthCompare: true - }) - ); - }); - - it('returns false if the non-widget text is only whitespace', function () { - fixtureSetup( - '

' + - ' link 1\t\n\r' + - ' link 2' + - ' link 3' + - ' link 4' + - '

' - ); - var link = document.getElementById('link'); - assert.isFalse( - axe.commons.dom.isInTextBlock(link, { - noLengthCompare: true - }) - ); + const link = document.getElementById('link'); + assert.isTrue(isInTextBlock(link, { noLengthCompare: true })); + }); + + it('returns false if the non-widget text is only whitespace', () => { + fixtureSetup(html` +

+ link 1 + + link 2 + link 3 + link 4 +

+ `); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link, { noLengthCompare: true })); + }); + }); + + describe('with options.includeInlineBlock: true', () => { + it('returns true if inline-block element has text sibling', () => { + const target = queryFixture(html` +
+ Hello world + +
+ `); + assert.isTrue(isInTextBlock(target, { includeInlineBlock: true })); + }); + + it('returns true if inline-block element has inline element sibling', () => { + const target = queryFixture(html` +
+ Hello world + +
+ `); + assert.isTrue(isInTextBlock(target, { includeInlineBlock: true })); + }); + + it('returns true if inline-block element has text sibling after it', () => { + const target = queryFixture(html` +
hello world
+ `); + assert.isTrue(isInTextBlock(target, { includeInlineBlock: true })); + }); + + it('returns true if inline-block element has both inline text and a widget sibling', () => { + const target = queryFixture(html` +
+ + Hello world, goodbye mars + +
+ `); + assert.isTrue(isInTextBlock(target, { includeInlineBlock: true })); + }); + + it('returns false the inline text sibling is on a different line', () => { + const target = queryFixture(html` +
+ Hello +
+ +
+ world +
+ `); + assert.isFalse(isInTextBlock(target, { includeInlineBlock: true })); + }); + + it('returns true if inline-block element has a sibling on the same line', () => { + const target = queryFixture(html` +
+ Hello +
+ world +
+ `); + assert.isFalse(isInTextBlock(target, { includeInlineBlock: true })); }); }); }); diff --git a/test/commons/dom/is-inert.js b/test/commons/dom/is-inert.js index ca4ba92e8..acb06f918 100644 --- a/test/commons/dom/is-inert.js +++ b/test/commons/dom/is-inert.js @@ -1,4 +1,5 @@ describe('dom.isInert', () => { + const html = axe.testUtils.html; const fixture = document.querySelector('#fixture'); const isInert = axe.commons.dom.isInert; const { queryFixture, flatTreeSetup } = axe.testUtils; @@ -30,7 +31,7 @@ describe('dom.isInert', () => { }); it('returns false for closed dialog', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html` Hello
World
`); @@ -39,7 +40,7 @@ describe('dom.isInert', () => { }); it('returns false for non-modal dialog', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html` Hello
World
`); @@ -48,7 +49,7 @@ describe('dom.isInert', () => { }); it('returns true for modal dialog', () => { - fixture.innerHTML = ` + fixture.innerHTML = html` Hello
World
`; @@ -60,9 +61,7 @@ describe('dom.isInert', () => { }); it('returns false for the modal dialog element', () => { - fixture.innerHTML = ` - Hello - `; + fixture.innerHTML = html` Hello `; document.querySelector('#target').showModal(); const tree = flatTreeSetup(fixture); const vNode = axe.utils.querySelectorAll(tree, '#target')[0]; @@ -71,7 +70,7 @@ describe('dom.isInert', () => { }); it('returns false for a descendant of the modal dialog', () => { - fixture.innerHTML = ` + fixture.innerHTML = html` Hello `; document.querySelector('#modal').showModal(); @@ -93,7 +92,7 @@ describe('dom.isInert', () => { describe('options.isAncestor', () => { it('return false for modal dialog', () => { - fixture.innerHTML = ` + fixture.innerHTML = html` Hello
World
`; diff --git a/test/commons/dom/is-modal-open.js b/test/commons/dom/is-modal-open.js index a9e83679e..6430c275f 100644 --- a/test/commons/dom/is-modal-open.js +++ b/test/commons/dom/is-modal-open.js @@ -1,82 +1,94 @@ -describe('dom.isModalOpen', function () { - 'use strict'; +describe('dom.isModalOpen', () => { + const html = axe.testUtils.html; - var fixtureSetup = axe.testUtils.fixtureSetup; - var isModalOpen = axe.commons.dom.isModalOpen; - var dialogElSupport = + const fixtureSetup = axe.testUtils.fixtureSetup; + const isModalOpen = axe.commons.dom.isModalOpen; + const dialogElSupport = typeof document.createElement('dialog').open !== 'undefined'; - it('returns true if there is a visible element with role=dialog', function () { + it('returns true if there is a visible element with role=dialog', () => { fixtureSetup('
Modal
'); assert.isTrue(isModalOpen()); }); - it('returns true if there is a visible element with aria-modal=true', function () { + it('returns true if there is a visible element with aria-modal=true', () => { fixtureSetup('
Modal
'); assert.isTrue(isModalOpen()); }); (dialogElSupport ? it : xit)( 'returns true if there is a visible dialog element', - function () { + () => { fixtureSetup('
Modal
'); assert.isTrue(isModalOpen()); } ); - it('returns true if there is a visible absolutely positioned element with >= 75% width/height', function () { + it('returns true if there is a visible absolutely positioned element with >= 75% width/height', () => { fixtureSetup( '
Modal
' ); assert.isTrue(isModalOpen()); }); - it('returns true if there is a visible absolutely positioned element with >= 75% width/height and is not the top most element', function () { - fixtureSetup( - '
' + - '
' + - '
Modal
' + - '
' + - '
' - ); + it('returns true if there is a visible absolutely positioned element with >= 75% width/height and is not the top most element', () => { + fixtureSetup(html` +
+
+
+ Modal +
+
+
+ `); assert.isTrue(isModalOpen()); }); - it('returns true if modal opens like a drawer', function () { - fixtureSetup( - '
' + - '
' + - '
Modal
' + - '
' + - '
' - ); + it('returns true if modal opens like a drawer', () => { + fixtureSetup(html` +
+
+
+ Modal +
+
+
+ `); assert.isTrue(isModalOpen()); }); - it('returns undefined if there is no modal', function () { + it('returns undefined if there is no modal', () => { fixtureSetup('
Modal
'); assert.isUndefined(isModalOpen()); }); - it('returns undefined if there is a hidden element with role=dialog', function () { + it('returns undefined if there is a hidden element with role=dialog', () => { fixtureSetup('
Modal
'); assert.isUndefined(isModalOpen()); }); - it('returns undefined if there is a hidden element with aria-modal=true', function () { + it('returns undefined if there is a hidden element with aria-modal=true', () => { fixtureSetup('
Modal
'); assert.isUndefined(isModalOpen()); }); (dialogElSupport ? it : xit)( 'returns undefined if there is a hidden dialog element', - function () { + () => { fixtureSetup('
Modal
'); assert.isUndefined(isModalOpen()); } ); - it('returns undefined if there is a visible absolutely positioned element with < 75% width/height', function () { + it('returns undefined if there is a visible absolutely positioned element with < 75% width/height', () => { fixtureSetup( '
small ' + - ' large' + - ' medium' + - '

'; - assert.isFalse(isMultiline(fixture.firstChild)); + it('handles single-line texts with varying font-sizes', () => { + fixture.innerHTML = html` +

+ small + large + medium +

+ `; + assert.isFalse(isMultiline(fixture.firstElementChild)); }); - describe('with non-text elements', function () { - it('is true when on a multiple lines', function () { - fixture.innerHTML = - '

' + - '
' + - '
' + - ' ' + - '

'; - assert.isTrue(isMultiline(fixture.firstChild)); + describe('with non-text elements', () => { + it('is true when on a multiple lines', () => { + fixture.innerHTML = html` +

+
+
+ +

+ `; + assert.isTrue(isMultiline(fixture.firstElementChild)); }); - it('is false when on a single line', function () { - fixture.innerHTML = - '

' + - ' Hello ' + - ' ' + - ' ' + - ' ' + - '

'; - assert.isFalse(isMultiline(fixture.firstChild)); + it('is false when on a single line', () => { + fixture.innerHTML = html` +

+ Hello + + + +

+ `; + assert.isFalse(isMultiline(fixture.firstElementChild)); }); }); }); diff --git a/test/commons/dom/is-natively-focusable.js b/test/commons/dom/is-natively-focusable.js index 128b98c99..17ea87779 100644 --- a/test/commons/dom/is-natively-focusable.js +++ b/test/commons/dom/is-natively-focusable.js @@ -4,20 +4,22 @@ describe('dom.isNativelyFocusable', () => { const { flatTreeSetup } = axe.testUtils; function hideByClipping(el) { - el.style.cssText = - 'position: absolute !important;' + - ' clip: rect(0px 0px 0px 0px); /* IE6, IE7 */' + - ' clip: rect(0px, 0px, 0px, 0px);'; + el.style.cssText = ` + position: absolute !important; + clip: rect(0px 0px 0px 0px); /* IE6, IE7 */ + clip: rect(0px, 0px, 0px, 0px); + `; } function hideByMovingOffScreen(el) { - el.style.cssText = - 'position:absolute;' + - ' left:-10000px;' + - ' top:auto;' + - ' width:1px;' + - ' height:1px;' + - ' overflow:hidden;'; + el.style.cssText = ` + position:absolute; + left:-10000px; + top:auto; + width:1px; + height:1px; + overflow:hidden; + `; } it('should return true for buttons with redundant tabindex', () => { diff --git a/test/commons/dom/is-node.js b/test/commons/dom/is-node.js index cdbc397a9..9e79eb141 100644 --- a/test/commons/dom/is-node.js +++ b/test/commons/dom/is-node.js @@ -1,8 +1,6 @@ -describe('dom.isNode', function () { - 'use strict'; - - it('nodes', function () { - var node; +describe('dom.isNode', () => { + it('nodes', () => { + let node; node = document; assert.isTrue(axe.commons.dom.isNode(node), 'Document'); @@ -25,8 +23,8 @@ describe('dom.isNode', function () { assert.isTrue(axe.commons.dom.isNode(node), 'Document fragments'); }); - it('non-nodes', function () { - var node; + it('non-nodes', () => { + let node; node = {}; assert.isFalse(axe.commons.dom.isNode(node)); diff --git a/test/commons/dom/is-offscreen.js b/test/commons/dom/is-offscreen.js index e755eda1a..4992d17e2 100644 --- a/test/commons/dom/is-offscreen.js +++ b/test/commons/dom/is-offscreen.js @@ -1,161 +1,274 @@ -describe('dom.isOffscreen', function () { - 'use strict'; - var fixture = document.getElementById('fixture'); - var shadowSupport = axe.testUtils.shadowSupport; +describe('dom.isOffscreen', () => { + const html = axe.testUtils.html; + const { isOffscreen } = axe.commons.dom; + const fixture = document.getElementById('fixture'); + const { queryFixture, fixtureSetup, flatTreeSetup } = axe.testUtils; - afterEach(function () { - fixture.innerHTML = ''; + afterEach(() => { document.body.style.direction = 'ltr'; }); - it('should detect elements positioned outside the left edge', function () { - fixture.innerHTML = - '
Offscreen?
'; - var el = document.getElementById('target'); + after(() => { + window.scrollTo(0, 0); + }); + + it('should be false for 0 height elements at the top of the viewport', () => { + assert.isFalse(isOffscreen(document.body)); + assert.isFalse(isOffscreen(document.documentElement)); - assert.isTrue(axe.commons.dom.isOffscreen(el)); + const vNode = queryFixture('
'); + assert.isFalse(isOffscreen(vNode)); }); - it('should detect elements positioned to but not beyond the left edge', function () { - fixture.innerHTML = - '
Offscreen?
'; - var el = document.getElementById('target'); + it('should detect elements positioned outside the left edge', () => { + const vNode = queryFixture( + '
Offscreen?
' + ); + assert.isTrue(isOffscreen(vNode)); + }); - assert.isTrue(axe.commons.dom.isOffscreen(el)); + it('should detect elements positioned to but not beyond the left edge', () => { + const vNode = queryFixture( + '
Offscreen?
' + ); + + assert.isTrue(isOffscreen(vNode)); }); - it('should not detect elements at the left edge with a zero width', function () { - fixture.innerHTML = - '
'; - var el = document.getElementById('target'); + it('should not detect elements at the left edge with a zero width', () => { + const vNode = queryFixture( + '
' + ); - assert.isFalse(axe.commons.dom.isOffscreen(el)); + assert.isFalse(isOffscreen(vNode)); }); - it('should detect elements positioned outside the top edge', function () { - fixture.innerHTML = - '
Offscreen?
'; - var el = document.getElementById('target'); - assert.isTrue(axe.commons.dom.isOffscreen(el)); + it('should detect elements positioned outside the top edge', () => { + const vNode = queryFixture( + '
Offscreen?
' + ); + assert.isTrue(isOffscreen(vNode)); }); - it('should never detect elements positioned outside the bottom edge', function () { - fixture.innerHTML = - '
Offscreen?
'; - var el = document.getElementById('target'); + it('should never detect elements positioned outside the bottom edge', () => { + const vNode = queryFixture( + '
Offscreen?
' + ); - assert.isFalse(axe.commons.dom.isOffscreen(el)); + assert.isFalse(isOffscreen(vNode)); }); - it('should detect elements positioned that bleed inside the left edge', function () { - fixture.innerHTML = - '
Offscreen?
'; - var el = document.getElementById('target'); + it('should detect elements positioned that bleed inside the left edge', () => { + const vNode = queryFixture( + '
Offscreen?
' + ); - assert.isFalse(axe.commons.dom.isOffscreen(el)); + assert.isFalse(isOffscreen(vNode)); }); - it('should detect elements positioned outside the right edge', function () { - fixture.innerHTML = - '
Offscreen?
'; - var el = document.getElementById('target'); + it('should detect elements positioned outside the right edge', () => { + const vNode = queryFixture( + '
Offscreen?
' + ); - assert.isFalse(axe.commons.dom.isOffscreen(el)); + assert.isFalse(isOffscreen(vNode)); }); - it('should detect elements positioned outside the top edge', function () { - fixture.innerHTML = - '
Offscreen?
'; - var el = document.getElementById('target'); + it('should detect elements positioned outside the top edge', () => { + const vNode = queryFixture( + '
Offscreen?
' + ); - assert.isFalse(axe.commons.dom.isOffscreen(el)); + assert.isFalse(isOffscreen(vNode)); }); - it('should detect elements positioned outside the bottom edge', function () { - fixture.innerHTML = - '
Offscreen?
'; - var el = document.getElementById('target'); + it('should detect elements positioned outside the bottom edge', () => { + const vNode = queryFixture( + '
Offscreen?
' + ); - assert.isFalse(axe.commons.dom.isOffscreen(el)); + assert.isFalse(isOffscreen(vNode)); }); - it('should detect elements that are made off-screen by a parent', function () { - fixture.innerHTML = - '
' + - '
Offscreen?
' + - '
'; + it('should detect elements that are made off-screen by a parent', () => { + const vNode = queryFixture(html` +
+
Offscreen?
+
+ `); - var el = document.getElementById('target'); - - assert.isTrue(axe.commons.dom.isOffscreen(el)); + assert.isTrue(isOffscreen(vNode)); }); - it('should NOT detect elements positioned outside the right edge on LTR documents', function () { - fixture.innerHTML = - '
Offscreen?
'; - var el = document.getElementById('target'); + it('should NOT detect elements positioned outside the right edge on LTR documents', () => { + const vNode = queryFixture( + '
Offscreen?
' + ); + + assert.isFalse(isOffscreen(vNode)); + }); - assert.isFalse(axe.commons.dom.isOffscreen(el)); + it('should detect elements positioned outside the right edge on RTL documents', () => { + document.body.style.direction = 'rtl'; + const vNode = queryFixture( + '
Offscreen?
' + ); + assert.isTrue(isOffscreen(vNode)); }); - it('should detect elements positioned outside the right edge on RTL documents', function () { + it('should NOT detect elements positioned outside the left edge on RTL documents', () => { document.body.style.direction = 'rtl'; - fixture.innerHTML = - '
Offscreen?
'; - var el = document.getElementById('target'); + const vNode = queryFixture( + '
Offscreen?
' + ); - assert.isTrue(axe.commons.dom.isOffscreen(el)); + assert.isFalse(isOffscreen(vNode)); }); - it('should NOT detect elements positioned outside the left edge on RTL documents', function () { + it('should detect elements positioned outside the right edge on RTL documents', () => { document.body.style.direction = 'rtl'; - fixture.innerHTML = - '
Offscreen?
'; - var el = document.getElementById('target'); - - assert.isFalse(axe.commons.dom.isOffscreen(el)); - }); - - it('should not detect elements positioned because of a scroll', function () { - fixture.innerHTML = - '
' + - '
goobye
' + - '
high
' + - '
hello
' + - '
'; - var viz = document.getElementById('visible'); - assert.isFalse(axe.commons.dom.isOffscreen(viz)); - var scrollme = document.getElementById('scrollme'); + const vNode = queryFixture( + '
Offscreen?
' + ); + + assert.isTrue(isOffscreen(vNode)); + }); + + it('should not detect elements positioned because of a scroll', () => { + fixtureSetup(html` +
+
goodbye
+
high
+
hello
+
+ `); + const viz = document.getElementById('visible'); + assert.isFalse(isOffscreen(viz)); + const scrollme = document.getElementById('scrollme'); scrollme.scrollIntoView(); - assert.isFalse(axe.commons.dom.isOffscreen(viz)); + assert.isFalse(isOffscreen(viz)); }); - it('should return undefined if actual ndoe is undefined', function () { - assert.isUndefined(axe.commons.dom.isOffscreen()); + it('should return undefined if actual node is undefined', () => { + assert.isUndefined(isOffscreen()); }); - (shadowSupport.v1 ? it : xit)( - 'should detect on screen shadow nodes', - function () { - fixture.innerHTML = '
'; - var shadow = fixture.querySelector('div').attachShadow({ mode: 'open' }); - shadow.innerHTML = '
Offscreen?
'; + it('should detect on screen shadow nodes', () => { + fixture.innerHTML = '
'; + const shadow = fixture.querySelector('div').attachShadow({ mode: 'open' }); + shadow.innerHTML = '
Offscreen?
'; + flatTreeSetup(fixture); + + const el = shadow.querySelector('#target'); + assert.isFalse(isOffscreen(el)); + }); - var el = shadow.querySelector('#target'); - assert.isFalse(axe.commons.dom.isOffscreen(el)); - } - ); + it('should detect off screen shadow nodes', () => { + fixture.innerHTML = '
'; + const shadow = fixture.querySelector('div').attachShadow({ mode: 'open' }); + shadow.innerHTML = + '
Offscreen?
'; + flatTreeSetup(fixture); - (shadowSupport.v1 ? it : xit)( - 'should detect off screen shadow nodes', - function () { - fixture.innerHTML = '
'; - var shadow = fixture.querySelector('div').attachShadow({ mode: 'open' }); - shadow.innerHTML = - '
Offscreen?
'; + const el = shadow.querySelector('#target'); + assert.isTrue(isOffscreen(el)); + }); - var el = shadow.querySelector('#target'); - assert.isTrue(axe.commons.dom.isOffscreen(el)); - } - ); + describe('positioned: fixed', () => { + it('should detect elements positioned outside the top edge', () => { + const vNode = queryFixture( + '
Offscreen?
' + ); + + assert.isTrue(isOffscreen(vNode)); + }); + + it('should detect elements positioned outside the top edge when scrolled', () => { + const vNode = queryFixture(html` +
+
+ Offscreen? +
+
+ `); + + assert.isTrue(isOffscreen(vNode)); + window.scrollTo(0, document.body.scrollHeight); + assert.isTrue(isOffscreen(vNode)); + }); + + it('should detect elements positioned outside the bottom edge', () => { + const vNode = queryFixture( + html`
+ Offscreen? +
` + ); + assert.isTrue(isOffscreen(vNode)); + }); + + it('should consider elements in the viewport, but beyond the window size as on screen', () => { + const vNode = queryFixture(html` +
+

Hello World

+
+ Offscreen? +
+
+ `); + assert.isFalse(isOffscreen(vNode)); + }); + + it('should detect elements positioned outside the right edge (LTR)', () => { + const vNode = queryFixture( + html`
+ Offscreen? +
` + ); + assert.isTrue(isOffscreen(vNode)); + }); + + it('should detect elements positioned outside the right edge (RTL)', () => { + document.body.style.direction = 'rtl'; + const vNode = queryFixture( + html`
+ Offscreen? +
` + ); + assert.isTrue(isOffscreen(vNode)); + }); + + it('should detect elements positioned outside the left edge (LTR)', () => { + const vNode = queryFixture( + html`
+ Offscreen? +
` + ); + assert.isTrue(isOffscreen(vNode)); + }); + + it('should detect elements positioned outside the left edge on RTL documents', () => { + document.body.style.direction = 'rtl'; + const vNode = queryFixture( + html`
+ Offscreen? +
` + ); + assert.isTrue(isOffscreen(vNode)); + }); + }); }); diff --git a/test/commons/dom/is-skip-link.js b/test/commons/dom/is-skip-link.js index 8827017b6..d379e5c88 100644 --- a/test/commons/dom/is-skip-link.js +++ b/test/commons/dom/is-skip-link.js @@ -1,10 +1,10 @@ -describe('dom.isSkipLink', function () { - 'use strict'; +describe('dom.isSkipLink', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var baseEl; + const fixture = document.getElementById('fixture'); + let baseEl; - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; if (baseEl) { @@ -12,116 +12,121 @@ describe('dom.isSkipLink', function () { } }); - it('should return true if the href points to an ID', function () { + it('should return true if the href points to an ID', () => { fixture.innerHTML = 'Click Here'; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('a'); + const node = fixture.querySelector('a'); assert.isTrue(axe.commons.dom.isSkipLink(node)); }); - it('should return false if the href points to another document', function () { + it('should return false if the href points to another document', () => { fixture.innerHTML = 'Click Here'; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('a'); + const node = fixture.querySelector('a'); assert.isFalse(axe.commons.dom.isSkipLink(node)); }); - it('should return true if the URI encoded href points to an element with an ID', function () { + it('should return true if the URI encoded href points to an element with an ID', () => { fixture.innerHTML = 'Click Here'; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('a'); + const node = fixture.querySelector('a'); assert.isTrue(axe.commons.dom.isSkipLink(node)); }); - it('should return true if the URI is an Angular skiplink', function () { + it('should return true if the URI is an Angular skiplink', () => { fixture.innerHTML = 'Click Here'; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('a'); + const node = fixture.querySelector('a'); assert.isTrue(axe.commons.dom.isSkipLink(node)); }); - it('should return false if the URI is angular #!', function () { + it('should return false if the URI is angular #!', () => { fixture.innerHTML = 'Click Here'; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('a'); + const node = fixture.querySelector('a'); assert.isFalse(axe.commons.dom.isSkipLink(node)); }); - it('should return false if the URI is angular #/', function () { + it('should return false if the URI is angular #/', () => { fixture.innerHTML = 'Click Here'; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('a'); + const node = fixture.querySelector('a'); assert.isFalse(axe.commons.dom.isSkipLink(node)); }); - it('should return true for multiple skip-links', function () { + it('should return true for multiple skip-links', () => { fixture.innerHTML = 'Click Here>Click Here>Click Here>'; axe._tree = axe.utils.getFlattenedTree(fixture); - var nodes = fixture.querySelectorAll('a'); + const nodes = fixture.querySelectorAll('a'); for (var i = 0; i < nodes.length; i++) { assert.isTrue(axe.commons.dom.isSkipLink(nodes[i])); } }); - it('should return true if the element is before a page link', function () { + it('should return true if the element is before a page link', () => { fixture.innerHTML = 'New Page'; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('#skip-link'); + const node = fixture.querySelector('#skip-link'); assert.isTrue(axe.commons.dom.isSkipLink(node)); }); - it('should return false if the element is after a page link', function () { + it('should return false if the element is after a page link', () => { fixture.innerHTML = 'New Page'; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('#skip-link'); + const node = fixture.querySelector('#skip-link'); assert.isFalse(axe.commons.dom.isSkipLink(node)); }); - it('should ignore links that start with `href=javascript`', function () { + it('should ignore links that start with `href=javascript`', () => { fixture.innerHTML = 'New Page'; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('#skip-link'); + const node = fixture.querySelector('#skip-link'); assert.isTrue(axe.commons.dom.isSkipLink(node)); }); - it('should return true for hash href that resolves to current page', function () { - fixture.innerHTML = - 'Click Here'; + it('should return true for hash href that resolves to current page', () => { + fixture.innerHTML = html`Click Here`; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('a'); + const node = fixture.querySelector('a'); assert.isTrue(axe.commons.dom.isSkipLink(node)); }); - it('should return true for absolute path hash href', function () { - var url = window.location.href; - fixture.innerHTML = 'Click Here'; + it('should return true for absolute path hash href', () => { + const url = window.location.href; + fixture.innerHTML = html`Click Here`; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('a'); + const node = fixture.querySelector('a'); assert.isTrue(axe.commons.dom.isSkipLink(node)); }); - it('should return false for absolute path href that points to another document', function () { - var origin = window.location.origin; - fixture.innerHTML = - 'Click Here'; + it('should return false for absolute path href that points to another document', () => { + const origin = window.location.origin; + fixture.innerHTML = html`Click Here`; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('a'); + const node = fixture.querySelector('a'); assert.isFalse(axe.commons.dom.isSkipLink(node)); }); - it('should return false for href with tag that points to another document', function () { + it('should return false for href with tag that points to another document', () => { baseEl = document.createElement('base'); baseEl.href = 'https://www.google.com/'; document.getElementsByTagName('head')[0].appendChild(baseEl); - fixture.innerHTML = - 'Click Here'; + // Use a stable path that won't accidentally match the angular skip-link + // regex (/#) regardless of what window.location.pathname is in the test runner. + // With base https://www.google.com/ the resolved URL becomes + // https://www.google.com/page.html#target, which is a different origin. + fixture.innerHTML = html`Click Here`; axe._tree = axe.utils.getFlattenedTree(fixture); - var node = fixture.querySelector('a'); + const node = fixture.querySelector('a'); assert.isFalse(axe.commons.dom.isSkipLink(node)); }); }); diff --git a/test/commons/dom/is-visible-on-screen.js b/test/commons/dom/is-visible-on-screen.js index 99437949f..4b0cb4121 100644 --- a/test/commons/dom/is-visible-on-screen.js +++ b/test/commons/dom/is-visible-on-screen.js @@ -1,314 +1,360 @@ -describe('dom.isVisibleOnScreen', function () { - 'use strict'; +describe('dom.isVisibleOnScreen', () => { + const html = axe.testUtils.html; - var fixture = document.querySelector('#fixture'); - var queryFixture = axe.testUtils.queryFixture; - var isIE11 = axe.testUtils.isIE11; - var shadowSupported = axe.testUtils.shadowSupport.v1; - var isVisibleOnScreen = axe.commons.dom.isVisibleOnScreen; + const fixture = document.querySelector('#fixture'); + const queryFixture = axe.testUtils.queryFixture; + const isIE11 = axe.testUtils.isIE11; + const isVisibleOnScreen = axe.commons.dom.isVisibleOnScreen; - it('should return true on statically-positioned, visible elements', function () { - var vNode = queryFixture('
Hello!
'); + it('should return true on statically-positioned, visible elements', () => { + const vNode = queryFixture('
Hello!
'); assert.isTrue(isVisibleOnScreen(vNode)); }); - it('should return true on absolutely positioned elements that are on-screen', function () { - var vNode = queryFixture( + it('should return true on absolutely positioned elements that are on-screen', () => { + const vNode = queryFixture( '
hi
' ); assert.isTrue(isVisibleOnScreen(vNode)); }); - it('should respect position: fixed', function () { - var vNode = queryFixture( + it('should respect position: fixed', () => { + const vNode = queryFixture( '
StickySticky
' ); assert.isTrue(isVisibleOnScreen(vNode)); }); - it('should properly calculate offsets according the offsetParent', function () { - var vNode = queryFixture( - '
' + - '
Hi
' + - '
' - ); + it('should properly calculate offsets according the offsetParent', () => { + const vNode = queryFixture(html` +
+
+ Hi +
+
+ `); assert.isTrue(isVisibleOnScreen(vNode)); }); - it('should return false if moved offscreen with left', function () { - var vNode = queryFixture( + it('should return false if moved offscreen with left', () => { + const vNode = queryFixture( '
Hi
' ); assert.isFalse(isVisibleOnScreen(vNode)); }); - it('should return false if moved offscreen with top', function () { - var vNode = queryFixture( + it('should return false if moved offscreen with top', () => { + const vNode = queryFixture( '
Hi
' ); assert.isFalse(isVisibleOnScreen(vNode)); }); - it('should return false on detached elements', function () { - var el = document.createElement('div'); + it('should return false on detached elements', () => { + const el = document.createElement('div'); el.innerHTML = 'I am not visible because I am detached!'; axe.testUtils.flatTreeSetup(el); assert.isFalse(isVisibleOnScreen(el)); }); - it('should return true on body', function () { + it('should return true on body', () => { axe.testUtils.flatTreeSetup(document.body); - var actual = isVisibleOnScreen(document.body); + const actual = isVisibleOnScreen(document.body); assert.isTrue(actual); }); - it('should return true on html', function () { + it('should return true on html', () => { axe.testUtils.flatTreeSetup(document.documentElement); - var actual = isVisibleOnScreen(document.documentElement); + const actual = isVisibleOnScreen(document.documentElement); assert.isTrue(actual); }); - it('should return false on STYLE tag', function () { - var vNode = queryFixture( + it('should return false on STYLE tag', () => { + const vNode = queryFixture( '' ); - var actual = isVisibleOnScreen(vNode); + const actual = isVisibleOnScreen(vNode); assert.isFalse(actual); }); - it('should return false on NOSCRIPT tag', function () { - var vNode = queryFixture( + it('should return false on NOSCRIPT tag', () => { + const vNode = queryFixture( '' ); - var actual = isVisibleOnScreen(vNode); + const actual = isVisibleOnScreen(vNode); assert.isFalse(actual); }); - it('should return false on TEMPLATE tag', function () { - var vNode = queryFixture( + it('should return false on TEMPLATE tag', () => { + const vNode = queryFixture( '' ); - var actual = isVisibleOnScreen(vNode); + const actual = isVisibleOnScreen(vNode); assert.isFalse(actual); }); - it('should return true if positioned statically but top/left is set', function () { - var vNode = queryFixture( - '
Hi
' - ); + it('should return true if positioned statically but top/left is set', () => { + const vNode = queryFixture(html` +
+ Hi +
+ `); assert.isTrue(isVisibleOnScreen(vNode)); }); - it('should not be affected by `aria-hidden`', function () { - var vNode = queryFixture( + it('should not be affected by `aria-hidden`', () => { + const vNode = queryFixture( '' ); assert.isTrue(isVisibleOnScreen(vNode)); }); - it('should not calculate position on parents', function () { - var vNode = queryFixture( - '
' + - '
Hi
' + - '
' - ); + it('should not calculate position on parents', () => { + const vNode = queryFixture(html` +
+
+ Hi +
+
+ `); assert.isTrue(isVisibleOnScreen(vNode)); }); - it('should know how `visibility` works', function () { - var vNode = queryFixture( - '
' + - '
Hi
' + - '
' - ); + it('should know how `visibility` works', () => { + const vNode = queryFixture(html` +
+
Hi
+
+ `); assert.isTrue(isVisibleOnScreen(vNode)); }); - it('should detect clip rect hidden text technique', function () { - var clip = - 'clip: rect(1px 1px 1px 1px);' + - 'clip: rect(1px, 1px, 1px, 1px);' + - 'width: 1px; height: 1px;' + - 'position: absolute;' + - 'overflow: hidden;'; + it('should detect clip rect hidden text technique', () => { + const clip = ` + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + width: 1px; height: 1px; + position: absolute; + overflow: hidden; + `; - var vNode = queryFixture('
Hi
'); + const vNode = queryFixture(html`
Hi
`); assert.isFalse(isVisibleOnScreen(vNode)); }); - it('should detect clip rect hidden text technique using position: fixed', function () { - var clip = - 'clip: rect(1px 1px 1px 1px);' + - 'clip: rect(1px, 1px, 1px, 1px);' + - 'width: 1px; height: 1px;' + - 'position: fixed;' + - 'overflow: hidden;'; + it('should detect clip rect hidden text technique using position: fixed', () => { + const clip = ` + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + width: 1px; height: 1px; + position: fixed; + overflow: hidden; + `; - var vNode = queryFixture('
Hi
'); + const vNode = queryFixture(html`
Hi
`); assert.isFalse(isVisibleOnScreen(vNode)); }); - it('should detect when clip is not applied because of positioning', function () { - var clip = - 'clip: rect(1px 1px 1px 1px);' + - 'clip: rect(1px, 1px, 1px, 1px);' + - 'position: relative;' + - 'overflow: hidden;'; + it('should detect when clip is not applied because of positioning', () => { + const clip = ` + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + position: relative; + overflow: hidden; + `; - var vNode = queryFixture('
Hi
'); + const vNode = queryFixture(html`
Hi
`); assert.isTrue(isVisibleOnScreen(vNode)); }); - it('should detect clip rect hidden text technique on parent', function () { - var clip = - 'clip: rect(1px 1px 1px 1px);' + - 'clip: rect(1px, 1px, 1px, 1px);' + - 'width: 1px; height: 1px;' + - 'position: absolute;' + - 'overflow: hidden;'; + it('should detect clip rect hidden text technique on parent', () => { + const clip = ` + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + width: 1px; height: 1px; + position: absolute; + overflow: hidden; + `; - var vNode = queryFixture( - '
' + '
Hi
' + '
' + const vNode = queryFixture( + html`
+
Hi
+
` ); assert.isFalse(isVisibleOnScreen(vNode)); }); - it('should detect when clip is not applied because of positioning on parent', function () { - var clip = - 'clip: rect(1px 1px 1px 1px);' + - 'clip: rect(1px, 1px, 1px, 1px);' + - 'position: relative;' + - 'overflow: hidden;'; + it('should detect when clip is not applied because of positioning on parent', () => { + const clip = ` + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + position: relative; + overflow: hidden; + `; - var vNode = queryFixture( - '
' + '
Hi
' + '
' + const vNode = queryFixture( + html`
+
Hi
+
` ); assert.isTrue(isVisibleOnScreen(vNode)); }); - it('should detect poorly hidden clip rects', function () { - var clip = - 'clip: rect(5px 1px 1px 5px);' + - 'clip: rect(5px, 1px, 1px, 5px);' + - 'width: 1px; height: 1px;' + - 'position: absolute;' + - 'overflow: hidden;'; + it('should detect poorly hidden clip rects', () => { + const clip = ` + clip: rect(5px 1px 1px 5px); + clip: rect(5px, 1px, 1px, 5px); + width: 1px; height: 1px; + position: absolute; + overflow: hidden; + `; - var vNode = queryFixture('
Hi
'); + const vNode = queryFixture(html`
Hi
`); assert.isFalse(isVisibleOnScreen(vNode)); }); - it('should return false for display: none', function () { - var vNode = queryFixture( + it('should return false for display: none', () => { + const vNode = queryFixture( '' ); assert.isFalse(isVisibleOnScreen(vNode)); }); - it('should return false for opacity: 0', function () { - var vNode = queryFixture( + it('should return false for opacity: 0', () => { + const vNode = queryFixture( '
Hello!
' ); assert.isFalse(isVisibleOnScreen(vNode)); }); - it('should return false for 0 height scrollable region', function () { - var vNode = queryFixture( + it('should return false for 0 height scrollable region', () => { + const vNode = queryFixture( '
Hello!
' ); assert.isFalse(isVisibleOnScreen(vNode)); }); - it('should return false for 0 width scrollable region', function () { - var vNode = queryFixture( + it('should return false for 0 width scrollable region', () => { + const vNode = queryFixture( '
Hello!
' ); assert.isFalse(isVisibleOnScreen(vNode)); }); - it('returns false for `AREA` without closest `MAP` element', function () { - var vNode = queryFixture( + it('returns false for `AREA` without closest `MAP` element', () => { + const vNode = queryFixture( '' ); - var actual = isVisibleOnScreen(vNode); + const actual = isVisibleOnScreen(vNode); assert.isFalse(actual); }); - it('returns false for `AREA` with closest `MAP` with no name attribute', function () { - var vNode = queryFixture( - '' + - '' + - '' - ); - var actual = isVisibleOnScreen(vNode); + it('returns false for `AREA` with closest `MAP` with no name attribute', () => { + const vNode = queryFixture(html` + + + + `); + const actual = isVisibleOnScreen(vNode); assert.isFalse(actual); }); - (shadowSupported ? it : xit)( - 'returns false for `AREA` element that is inside shadowDOM', - function () { - fixture.innerHTML = '
'; - var container = fixture.querySelector('#container'); - var shadow = container.attachShadow({ mode: 'open' }); - shadow.innerHTML = - '' + - '' + - ''; - axe.testUtils.flatTreeSetup(fixture); - - var target = shadow.querySelector('#target'); - var actual = isVisibleOnScreen(target); - assert.isFalse(actual); - } - ); + it('returns false for `AREA` element that is inside shadowDOM', () => { + fixture.innerHTML = '
'; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); + shadow.innerHTML = html` + + + + `; + axe.testUtils.flatTreeSetup(fixture); + + const target = shadow.querySelector('#target'); + const actual = isVisibleOnScreen(target); + assert.isFalse(actual); + }); - it('returns false for `AREA` with closest `MAP` with name but not referred by an `IMG` usemap attribute', function () { - var vNode = queryFixture( - '' + - '' + - '' + - 'MDN infographic' - ); - var actual = isVisibleOnScreen(vNode); + it('returns false for `AREA` with closest `MAP` with name but not referred by an `IMG` usemap attribute', () => { + const vNode = queryFixture(html` + + + + MDN infographic + `); + const actual = isVisibleOnScreen(vNode); assert.isFalse(actual); }); - it('returns false for `AREA` with `MAP` and used in `IMG` which is not visible', function () { - var vNode = queryFixture( - '' + - '' + - '' + - 'MDN infographic' - ); - var actual = isVisibleOnScreen(vNode); + it('returns false for `AREA` with `MAP` and used in `IMG` which is not visible', () => { + const vNode = queryFixture(html` + + + + MDN infographic + `); + const actual = isVisibleOnScreen(vNode); assert.isFalse(actual); }); - it('returns true for `AREA` with `MAP` and used in `IMG` which is visible', function () { - var vNode = queryFixture( - '' + - '' + - '' + - 'MDN infographic' - ); - var actual = isVisibleOnScreen(vNode); + it('returns true for `AREA` with `MAP` and used in `IMG` which is visible', () => { + const vNode = queryFixture(html` + + + + MDN infographic + `); + const actual = isVisibleOnScreen(vNode); assert.isTrue(actual); }); @@ -316,8 +362,8 @@ describe('dom.isVisibleOnScreen', function () { // MDN and caniuse.com give different results... (isIE11 ? it.skip : it)( 'should detect clip-path hidden text technique', - function () { - var vNode = queryFixture( + () => { + const vNode = queryFixture( '
Hi
' ); @@ -327,111 +373,105 @@ describe('dom.isVisibleOnScreen', function () { (isIE11 ? it.skip : it)( 'should detect clip-path hidden text technique on parent', - function () { - var vNode = queryFixture( - '
' + - '
Hi
' + - '
' - ); + () => { + const vNode = queryFixture(html` +
+
Hi
+
+ `); assert.isFalse(isVisibleOnScreen(vNode)); } ); - (shadowSupported ? it : xit)( - 'should correctly handle visible slotted elements', - function () { - function createContentSlotted() { - var group = document.createElement('div'); - group.innerHTML = '
Stuff
'; - return group; - } - function makeShadowTree(node) { - var root = node.attachShadow({ mode: 'open' }); - var div = document.createElement('div'); - root.appendChild(div); - div.appendChild(createContentSlotted()); - } - fixture.innerHTML = ''; - makeShadowTree(fixture.firstChild); - var tree = axe.utils.getFlattenedTree(fixture.firstChild); - var el = axe.utils.querySelectorAll(tree, 'a')[0]; - assert.isTrue(isVisibleOnScreen(el.actualNode)); + it('should correctly handle visible slotted elements', () => { + function createContentSlotted() { + const group = document.createElement('div'); + group.innerHTML = '
Stuff
'; + return group; } - ); - (shadowSupported ? it : xit)( - 'should correctly handle hidden slotted elements', - function () { - function createContentSlotted() { - var group = document.createElement('div'); - group.innerHTML = - ''; - return group; - } - function makeShadowTree(node) { - var root = node.attachShadow({ mode: 'open' }); - var div = document.createElement('div'); - root.appendChild(div); - div.appendChild(createContentSlotted()); - } - fixture.innerHTML = ''; - makeShadowTree(fixture.firstChild); - var tree = axe.utils.getFlattenedTree(fixture.firstChild); - var el = axe.utils.querySelectorAll(tree, 'a')[0]; - assert.isFalse(isVisibleOnScreen(el.actualNode)); + function makeShadowTree(node) { + const root = node.attachShadow({ mode: 'open' }); + const div = document.createElement('div'); + root.appendChild(div); + div.appendChild(createContentSlotted()); } - ); + fixture.innerHTML = ''; + makeShadowTree(fixture.firstChild); + const tree = axe.utils.getFlattenedTree(fixture.firstChild); + const el = axe.utils.querySelectorAll(tree, 'a')[0]; + assert.isTrue(isVisibleOnScreen(el.actualNode)); + }); + it('should correctly handle hidden slotted elements', () => { + function createContentSlotted() { + const group = document.createElement('div'); + group.innerHTML = + ''; + return group; + } + function makeShadowTree(node) { + const root = node.attachShadow({ mode: 'open' }); + const div = document.createElement('div'); + root.appendChild(div); + div.appendChild(createContentSlotted()); + } + fixture.innerHTML = ''; + makeShadowTree(fixture.firstChild); + const tree = axe.utils.getFlattenedTree(fixture.firstChild); + const el = axe.utils.querySelectorAll(tree, 'a')[0]; + assert.isFalse(isVisibleOnScreen(el.actualNode)); + }); - it('should return false for screen reader only technique', function () { - var vNode = queryFixture( + it('should return false for screen reader only technique', () => { + const vNode = queryFixture( '
Visually Hidden
' ); assert.isFalse(isVisibleOnScreen(vNode)); }); - it('should return false for element outside "overflow:hidden"', function () { - var vNode = queryFixture( + it('should return false for element outside "overflow:hidden"', () => { + const vNode = queryFixture( '
Visually Hidden
' ); assert.isFalse(isVisibleOnScreen(vNode)); }); - describe('SerialVirtualNode', function () { - it('should return true on statically-positioned, visible elements', function () { - var vNode = new axe.SerialVirtualNode({ + describe('SerialVirtualNode', () => { + it('should return true on statically-positioned, visible elements', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'div' }); assert.isTrue(isVisibleOnScreen(vNode)); }); - it('should return false on STYLE tag', function () { - var vNode = new axe.SerialVirtualNode({ + it('should return false on STYLE tag', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'style' }); - var actual = isVisibleOnScreen(vNode); + const actual = isVisibleOnScreen(vNode); assert.isFalse(actual); }); - it('should return false on NOSCRIPT tag', function () { - var vNode = new axe.SerialVirtualNode({ + it('should return false on NOSCRIPT tag', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'noscript' }); - var actual = isVisibleOnScreen(vNode); + const actual = isVisibleOnScreen(vNode); assert.isFalse(actual); }); - it('should return false on TEMPLATE tag', function () { - var vNode = new axe.SerialVirtualNode({ + it('should return false on TEMPLATE tag', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'template' }); - var actual = isVisibleOnScreen(vNode); + const actual = isVisibleOnScreen(vNode); assert.isFalse(actual); }); - it('should not be affected by `aria-hidden`', function () { - var vNode = new axe.SerialVirtualNode({ + it('should not be affected by `aria-hidden`', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'div', attributes: { 'aria-hidden': true diff --git a/test/commons/dom/is-visible-to-screenreader.js b/test/commons/dom/is-visible-to-screenreader.js index 3586366a2..02ee2ae97 100644 --- a/test/commons/dom/is-visible-to-screenreader.js +++ b/test/commons/dom/is-visible-to-screenreader.js @@ -1,220 +1,238 @@ -describe('dom.isVisibleToScreenReaders', function () { - 'use strict'; +describe('dom.isVisibleToScreenReaders', () => { + const html = axe.testUtils.html; - var fixture = document.querySelector('#fixture'); - var queryFixture = axe.testUtils.queryFixture; - var shadowSupported = axe.testUtils.shadowSupport.v1; - var isVisibleToScreenReaders = axe.commons.dom.isVisibleToScreenReaders; + const fixture = document.querySelector('#fixture'); + const queryFixture = axe.testUtils.queryFixture; + const isVisibleToScreenReaders = axe.commons.dom.isVisibleToScreenReaders; function createContentHidden() { - var group = document.createElement('div'); + const group = document.createElement('div'); group.innerHTML = ''; return group; } function makeShadowTreeHidden(node) { - var root = node.attachShadow({ mode: 'open' }); - var div = document.createElement('div'); + const root = node.attachShadow({ mode: 'open' }); + const div = document.createElement('div'); div.className = 'parent'; root.appendChild(div); div.appendChild(createContentHidden()); } - it('should return false on detached elements', function () { - var el = document.createElement('div'); + it('should return false on detached elements', () => { + const el = document.createElement('div'); el.innerHTML = 'I am not visible because I am detached!'; axe.testUtils.flatTreeSetup(el); assert.isFalse(isVisibleToScreenReaders(el)); }); - it('should return true on body', function () { + it('should return true on body', () => { axe.testUtils.flatTreeSetup(document.body); - var actual = isVisibleToScreenReaders(document.body); + const actual = isVisibleToScreenReaders(document.body); assert.isTrue(actual); }); - it('should return true on html', function () { + it('should return true on html', () => { axe.testUtils.flatTreeSetup(document.documentElement); - var actual = isVisibleToScreenReaders(document.documentElement); + const actual = isVisibleToScreenReaders(document.documentElement); assert.isTrue(actual); }); - it('should return true for visible element', function () { - var vNode = queryFixture('
Visible
'); + it('should return true for visible element', () => { + const vNode = queryFixture('
Visible
'); assert.isTrue(isVisibleToScreenReaders(vNode)); }); - it('should return true for visible area element', function () { - var vNode = queryFixture( - '' + - '' + - '' + - '' - ); + it('should return true for visible area element', () => { + const vNode = queryFixture(html` + + + + + `); assert.isTrue(isVisibleToScreenReaders(vNode)); }); - it('should return false if `aria-hidden` is set', function () { - var vNode = queryFixture( + it('should return false if `aria-hidden` is set', () => { + const vNode = queryFixture( '' ); assert.isFalse(isVisibleToScreenReaders(vNode)); }); - it('should return false if `inert` is set', function () { - var vNode = queryFixture( + it('should return false if `inert` is set', () => { + const vNode = queryFixture( '
Hidden from screen readers
' ); assert.isFalse(isVisibleToScreenReaders(vNode)); }); - it('should return false if `display: none` is set', function () { - var vNode = queryFixture( + it('should return false if `display: none` is set', () => { + const vNode = queryFixture( '' ); assert.isFalse(isVisibleToScreenReaders(vNode)); }); - it('should return false if `aria-hidden` is set on parent', function () { - var vNode = queryFixture( + it('should return false if `aria-hidden` is set on parent', () => { + const vNode = queryFixture( '' ); assert.isFalse(isVisibleToScreenReaders(vNode)); }); - it('should know how `visibility` works', function () { - var vNode = queryFixture( - '
' + - '
Hi
' + - '
' - ); + it('should know how `visibility` works', () => { + const vNode = queryFixture(html` +
+
Hi
+
+ `); assert.isTrue(isVisibleToScreenReaders(vNode)); }); - it('returns false for `AREA` without closest `MAP` element', function () { - var vNode = queryFixture( + it('returns false for `AREA` without closest `MAP` element', () => { + const vNode = queryFixture( '' ); - var actual = isVisibleToScreenReaders(vNode); + const actual = isVisibleToScreenReaders(vNode); assert.isFalse(actual); }); - it('returns false for `AREA` with closest `MAP` with no name attribute', function () { - var vNode = queryFixture( - '' + - '' + - '' - ); - var actual = isVisibleToScreenReaders(vNode); + it('returns false for `AREA` with closest `MAP` with no name attribute', () => { + const vNode = queryFixture(html` + + + + `); + const actual = isVisibleToScreenReaders(vNode); assert.isFalse(actual); }); - (shadowSupported ? it : xit)( - 'returns false for `AREA` element that is inside shadowDOM', - function () { - fixture.innerHTML = '
'; - var container = fixture.querySelector('#container'); - var shadow = container.attachShadow({ mode: 'open' }); - shadow.innerHTML = - '' + - '' + - ''; - axe.testUtils.flatTreeSetup(fixture); + it('returns false for `AREA` element that is inside shadowDOM', () => { + fixture.innerHTML = '
'; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); + shadow.innerHTML = html` + + + + `; + axe.testUtils.flatTreeSetup(fixture); - var target = shadow.querySelector('#target'); - var actual = isVisibleToScreenReaders(target); - assert.isFalse(actual); - } - ); + const target = shadow.querySelector('#target'); + const actual = isVisibleToScreenReaders(target); + assert.isFalse(actual); + }); - it('returns false for `AREA` with closest `MAP` with name but not referred by an `IMG` usemap attribute', function () { - var vNode = queryFixture( - '' + - '' + - '' + - 'MDN infographic' - ); - var actual = isVisibleToScreenReaders(vNode); + it('returns false for `AREA` with closest `MAP` with name but not referred by an `IMG` usemap attribute', () => { + const vNode = queryFixture(html` + + + + MDN infographic + `); + const actual = isVisibleToScreenReaders(vNode); assert.isFalse(actual); }); - it('returns false for `AREA` with `MAP` and used in `IMG` which is not visible', function () { - var vNode = queryFixture( - '' + - '' + - '' + - 'MDN infographic' - ); - var actual = isVisibleToScreenReaders(vNode); + it('returns false for `AREA` with `MAP` and used in `IMG` which is not visible', () => { + const vNode = queryFixture(html` + + + + MDN infographic + `); + const actual = isVisibleToScreenReaders(vNode); assert.isFalse(actual); }); - it('returns true for `AREA` with `MAP` and used in `IMG` which is visible', function () { - var vNode = queryFixture( - '' + - '' + - '' + - 'MDN infographic' - ); - var actual = isVisibleToScreenReaders(vNode); + it('returns true for `AREA` with `MAP` and used in `IMG` which is visible', () => { + const vNode = queryFixture(html` + + + + MDN infographic + `); + const actual = isVisibleToScreenReaders(vNode); assert.isTrue(actual); }); - (shadowSupported ? it : xit)( - 'not hidden: should work when the element is inside shadow DOM', - function () { - var tree, node; - // shadow DOM v1 - note: v0 is compatible with this code, so no need - // to specifically test this - fixture.innerHTML = '
'; - makeShadowTreeHidden(fixture.firstChild); - tree = axe.utils.getFlattenedTree(fixture.firstChild); - node = axe.utils.querySelectorAll(tree, 'input')[0]; - assert.isTrue(isVisibleToScreenReaders(node)); - } - ); + it('not hidden: should work when the element is inside shadow DOM', () => { + let tree, node; + // shadow DOM v1 - note: v0 is compatible with this code, so no need + // to specifically test this + fixture.innerHTML = '
'; + makeShadowTreeHidden(fixture.firstChild); + tree = axe.utils.getFlattenedTree(fixture.firstChild); + node = axe.utils.querySelectorAll(tree, 'input')[0]; + assert.isTrue(isVisibleToScreenReaders(node)); + }); - (shadowSupported ? it : xit)( - 'hidden: should work when the element is inside shadow DOM', - function () { - var tree, node; - // shadow DOM v1 - note: v0 is compatible with this code, so no need - // to specifically test this - fixture.innerHTML = '
'; - makeShadowTreeHidden(fixture.firstChild); - tree = axe.utils.getFlattenedTree(fixture.firstChild); - node = axe.utils.querySelectorAll(tree, 'input')[0]; - assert.isFalse(isVisibleToScreenReaders(node)); - } - ); + it('hidden: should work when the element is inside shadow DOM', () => { + let tree, node; + // shadow DOM v1 - note: v0 is compatible with this code, so no need + // to specifically test this + fixture.innerHTML = '
'; + makeShadowTreeHidden(fixture.firstChild); + tree = axe.utils.getFlattenedTree(fixture.firstChild); + node = axe.utils.querySelectorAll(tree, 'input')[0]; + assert.isFalse(isVisibleToScreenReaders(node)); + }); - (shadowSupported ? it : xit)( - 'should work with hidden slotted elements', - function () { - function createContentSlotted() { - var group = document.createElement('div'); - group.innerHTML = - ''; - return group; - } - function makeShadowTree(node) { - var root = node.attachShadow({ mode: 'open' }); - var div = document.createElement('div'); - root.appendChild(div); - div.appendChild(createContentSlotted()); - } - fixture.innerHTML = ''; - makeShadowTree(fixture.firstChild); - var tree = axe.utils.getFlattenedTree(fixture.firstChild); - var vNode = axe.utils.querySelectorAll(tree, 'a')[0]; - assert.isFalse(isVisibleToScreenReaders(vNode)); + it('should work with hidden slotted elements', () => { + function createContentSlotted() { + const group = document.createElement('div'); + group.innerHTML = + ''; + return group; + } + function makeShadowTree(node) { + const root = node.attachShadow({ mode: 'open' }); + const div = document.createElement('div'); + root.appendChild(div); + div.appendChild(createContentSlotted()); } - ); + fixture.innerHTML = ''; + makeShadowTree(fixture.firstChild); + const tree = axe.utils.getFlattenedTree(fixture.firstChild); + const vNode = axe.utils.querySelectorAll(tree, 'a')[0]; + assert.isFalse(isVisibleToScreenReaders(vNode)); + }); - describe('SerialVirtualNode', function () { - it('should return false if `aria-hidden` is set', function () { - var vNode = new axe.SerialVirtualNode({ + describe('SerialVirtualNode', () => { + it('should return false if `aria-hidden` is set', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'div', attributes: { 'aria-hidden': true @@ -223,11 +241,11 @@ describe('dom.isVisibleToScreenReaders', function () { assert.isFalse(isVisibleToScreenReaders(vNode)); }); - it('should return false if `aria-hidden` is set on parent', function () { - var vNode = new axe.SerialVirtualNode({ + it('should return false if `aria-hidden` is set on parent', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'div' }); - var parentVNode = new axe.SerialVirtualNode({ + const parentVNode = new axe.SerialVirtualNode({ nodeName: 'div', attributes: { 'aria-hidden': true @@ -238,8 +256,8 @@ describe('dom.isVisibleToScreenReaders', function () { assert.isFalse(isVisibleToScreenReaders(vNode)); }); - it('should return false if `inert` is set', function () { - var vNode = new axe.SerialVirtualNode({ + it('should return false if `inert` is set', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'div', attributes: { inert: true @@ -248,11 +266,11 @@ describe('dom.isVisibleToScreenReaders', function () { assert.isFalse(isVisibleToScreenReaders(vNode)); }); - it('should return false if `inert` is set on parent', function () { - var vNode = new axe.SerialVirtualNode({ + it('should return false if `inert` is set on parent', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'div' }); - var parentVNode = new axe.SerialVirtualNode({ + const parentVNode = new axe.SerialVirtualNode({ nodeName: 'div', attributes: { inert: true diff --git a/test/commons/dom/is-visible.js b/test/commons/dom/is-visible.js index db85fafc3..7bdb81679 100644 --- a/test/commons/dom/is-visible.js +++ b/test/commons/dom/is-visible.js @@ -1,12 +1,12 @@ -describe('dom.isVisible', function () { - 'use strict'; +describe('dom.isVisible', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var queryFixture = axe.testUtils.queryFixture; - var shadowSupported = axe.testUtils.shadowSupport.v1; - var computedStyleStub; + const fixture = document.getElementById('fixture'); + const queryFixture = axe.testUtils.queryFixture; + let computedStyleStub; + const flatTreeSetup = axe.testUtils.flatTreeSetup; - afterEach(function () { + afterEach(() => { document.getElementById('fixture').innerHTML = ''; axe._tree = undefined; @@ -16,655 +16,762 @@ describe('dom.isVisible', function () { } }); - describe('default usage', function () { + describe('default usage', () => { // Firefox returns `null` if accessed inside a hidden iframe - it('should return false if computedStyle return null for whatever reason', function () { + it('should return false if computedStyle return null for whatever reason', () => { computedStyleStub = sinon.stub(window, 'getComputedStyle').returns(null); - var el = document.createElement('div'); + const el = document.createElement('div'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); - it('should return true on statically-positioned, visible elements', function () { + it('should return true on statically-positioned, visible elements', () => { fixture.innerHTML = '
Hello!
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); - it('should return true on absolutely positioned elements that are on-screen', function () { + it('should return true on absolutely positioned elements that are on-screen', () => { fixture.innerHTML = '
hi
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); - it('should respect position: fixed', function () { + it('should respect position: fixed', () => { fixture.innerHTML = '
StickySticky
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); - it('should properly calculate offsets according the offsetParent', function () { - fixture.innerHTML = - '
' + - '
Hi
' + - '
'; - var el = document.getElementById('target'); + it('should properly calculate offsets according the offsetParent', () => { + fixture.innerHTML = html` +
+
+ Hi +
+
+ `; + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); - it('should return false if moved offscreen with left', function () { + it('should return false if moved offscreen with left', () => { fixture.innerHTML = '
Hi
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); - it('should return false if moved offscreen with top', function () { + it('should return false if moved offscreen with top', () => { fixture.innerHTML = '
Hi
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); - it('should return false on detached elements', function () { - var el = document.createElement('div'); + it('should return false on detached elements', () => { + const el = document.createElement('div'); el.innerHTML = 'I am not visible because I am detached!'; assert.isFalse(axe.commons.dom.isVisible(el)); }); - it('should return true on a document', function () { + it('should return true on a document', () => { assert.isTrue(axe.commons.dom.isVisible(document)); }); - it('should return false on STYLE tag', function () { - var vNode = queryFixture( + it('should return false on STYLE tag', () => { + const vNode = queryFixture( '' ); - var actual = axe.commons.dom.isVisible(vNode.actualNode); + const actual = axe.commons.dom.isVisible(vNode.actualNode); assert.isFalse(actual); }); - it('should return false on NOSCRIPT tag', function () { - var vNode = queryFixture( + it('should return false on NOSCRIPT tag', () => { + const vNode = queryFixture( '' ); - var actual = axe.commons.dom.isVisible(vNode.actualNode); + const actual = axe.commons.dom.isVisible(vNode.actualNode); assert.isFalse(actual); }); - it('should return false on TEMPLATE tag', function () { - var vNode = queryFixture( + it('should return false on TEMPLATE tag', () => { + const vNode = queryFixture( '' ); - var actual = axe.commons.dom.isVisible(vNode.actualNode); + const actual = axe.commons.dom.isVisible(vNode.actualNode); assert.isFalse(actual); }); - it('should return true if positioned statically but top/left is set', function () { - fixture.innerHTML = - '
Hi
'; - var el = document.getElementById('target'); + it('should return true if positioned statically but top/left is set', () => { + fixture.innerHTML = html` +
+ Hi +
+ `; + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); - it('should not be affected by `aria-hidden`', function () { + it('should not be affected by `aria-hidden`', () => { fixture.innerHTML = ''; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); - it('should not calculate position on parents', function () { - fixture.innerHTML = - '
' + - '
Hi
' + - '
'; + it('should not calculate position on parents', () => { + fixture.innerHTML = html` +
+
+ Hi +
+
+ `; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); - it('should know how `visibility` works', function () { - fixture.innerHTML = - '
' + - '
Hi
' + - '
'; + it('should know how `visibility` works', () => { + fixture.innerHTML = html` +
+
Hi
+
+ `; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); - it('should detect clip rect hidden text technique', function () { - var el, - clip = - 'clip: rect(1px 1px 1px 1px);' + - 'clip: rect(1px, 1px, 1px, 1px);' + - 'width: 1px; height: 1px;' + - 'position: absolute;' + - 'overflow: hidden;'; + it('should detect clip rect hidden text technique', () => { + let el; + + const clip = ` + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + width: 1px; height: 1px; + position: absolute; + overflow: hidden; + `; - fixture.innerHTML = '
Hi
'; + fixture.innerHTML = html`
Hi
`; el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); - it('should detect clip rect hidden text technique using position: fixed', function () { - var el, - clip = - 'clip: rect(1px 1px 1px 1px);' + - 'clip: rect(1px, 1px, 1px, 1px);' + - 'width: 1px; height: 1px;' + - 'position: fixed;' + - 'overflow: hidden;'; + it('should detect clip rect hidden text technique using position: fixed', () => { + let el; - fixture.innerHTML = '
Hi
'; + const clip = ` + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + width: 1px; height: 1px; + position: fixed; + overflow: hidden; + `; + + fixture.innerHTML = html`
Hi
`; el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); - it('should detect when clip is not applied because of positioning', function () { - var el, - clip = - 'clip: rect(1px 1px 1px 1px);' + - 'clip: rect(1px, 1px, 1px, 1px);' + - 'position: relative;' + - 'overflow: hidden;'; + it('should detect when clip is not applied because of positioning', () => { + let el; + + const clip = ` + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + position: relative; + overflow: hidden; + `; - fixture.innerHTML = '
Hi
'; + fixture.innerHTML = html`
Hi
`; el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); - it('should detect clip rect hidden text technique on parent', function () { - var el, - clip = - 'clip: rect(1px 1px 1px 1px);' + - 'clip: rect(1px, 1px, 1px, 1px);' + - 'width: 1px; height: 1px;' + - 'position: absolute;' + - 'overflow: hidden;'; + it('should detect clip rect hidden text technique on parent', () => { + let el; - fixture.innerHTML = - '
' + '
Hi
' + '
'; + const clip = ` + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + width: 1px; height: 1px; + position: absolute; + overflow: hidden; + `; + + fixture.innerHTML = html`
+
Hi
+
`; el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); - it('should detect when clip is not applied because of positioning on parent', function () { - var el, - clip = - 'clip: rect(1px 1px 1px 1px);' + - 'clip: rect(1px, 1px, 1px, 1px);' + - 'position: relative;' + - 'overflow: hidden;'; + it('should detect when clip is not applied because of positioning on parent', () => { + let el; - fixture.innerHTML = - '
' + '
Hi
' + '
'; + const clip = ` + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + position: relative; + overflow: hidden; + `; + + fixture.innerHTML = html`
+
Hi
+
`; el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); - it('should detect poorly hidden clip rects', function () { - var el, - clip = - 'clip: rect(5px 1px 1px 5px);' + - 'clip: rect(5px, 1px, 1px, 5px);' + - 'width: 1px; height: 1px;' + - 'position: absolute;' + - 'overflow: hidden;'; + it('should detect poorly hidden clip rects', () => { + let el; + + const clip = ` + clip: rect(5px 1px 1px 5px); + clip: rect(5px, 1px, 1px, 5px); + width: 1px; height: 1px; + position: absolute; + overflow: hidden; + `; - fixture.innerHTML = '
Hi
'; + fixture.innerHTML = html`
Hi
`; el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); - it('should return false for display: none', function () { + it('should return false for display: none', () => { fixture.innerHTML = ''; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); - it('should return false for opacity: 0', function () { + it('should return false for opacity: 0', () => { fixture.innerHTML = '
Hello!
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); - it('should return false for opacity: 0', function () { + it('should return false for opacity: 0', () => { fixture.innerHTML = '
Hello!
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); - it('should return false for 0 height scrollable region', function () { + it('should return false for 0 height scrollable region', () => { fixture.innerHTML = '
Hello!
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); - it('should return false for 0 width scrollable region', function () { + it('should return false for 0 width scrollable region', () => { fixture.innerHTML = '
Hello!
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); - it('returns false for `AREA` without closest `MAP` element', function () { - var vNode = queryFixture( + it('returns false for `AREA` without closest `MAP` element', () => { + const vNode = queryFixture( '' ); - var actual = axe.commons.dom.isVisible(vNode.actualNode); + const actual = axe.commons.dom.isVisible(vNode.actualNode); assert.isFalse(actual); }); - it('returns false for `AREA` with closest `MAP` with no name attribute', function () { - var vNode = queryFixture( - '' + - '' + - '' - ); - var actual = axe.commons.dom.isVisible(vNode.actualNode); + it('returns false for `AREA` with closest `MAP` with no name attribute', () => { + const vNode = queryFixture(html` + + + + `); + const actual = axe.commons.dom.isVisible(vNode.actualNode); assert.isFalse(actual); }); - (shadowSupported ? it : xit)( - 'returns false for `AREA` element that is inside shadowDOM', - function () { - fixture.innerHTML = '
'; - var container = fixture.querySelector('#container'); - var shadow = container.attachShadow({ mode: 'open' }); - shadow.innerHTML = - '' + - '' + - ''; - axe.testUtils.flatTreeSetup(fixture); - - var target = shadow.querySelector('#target'); - var actual = axe.commons.dom.isVisible(target); - assert.isFalse(actual); - } - ); - - it('returns false for `AREA` with closest `MAP` with name but not referred by an `IMG` usemap attribute', function () { - var vNode = queryFixture( - '' + - '' + - '' + - 'MDN infographic' - ); - var actual = axe.commons.dom.isVisible(vNode.actualNode); + it('returns false for `AREA` element that is inside shadowDOM', () => { + fixture.innerHTML = '
'; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); + shadow.innerHTML = html` + + + + `; + axe.testUtils.flatTreeSetup(fixture); + + const target = shadow.querySelector('#target'); + const actual = axe.commons.dom.isVisible(target); assert.isFalse(actual); }); - it('returns false for `AREA` with `MAP` and used in `IMG` which is not visible', function () { - var vNode = queryFixture( - '' + - '' + - '' + - 'MDN infographic' - ); - var actual = axe.commons.dom.isVisible(vNode.actualNode); + it('returns false for `AREA` with closest `MAP` with name but not referred by an `IMG` usemap attribute', () => { + const vNode = queryFixture(html` + + + + MDN infographic + `); + const actual = axe.commons.dom.isVisible(vNode.actualNode); assert.isFalse(actual); }); - it('returns true for `AREA` with `MAP` and used in `IMG` which is visible', function () { - var vNode = queryFixture( - '' + - '' + - '' + - 'MDN infographic' - ); - var actual = axe.commons.dom.isVisible(vNode.actualNode); + it('returns false for `AREA` with `MAP` and used in `IMG` which is not visible', () => { + const vNode = queryFixture(html` + + + + MDN infographic + `); + const actual = axe.commons.dom.isVisible(vNode.actualNode); + assert.isFalse(actual); + }); + + it('returns true for `AREA` with `MAP` and used in `IMG` which is visible', () => { + const vNode = queryFixture(html` + + + + MDN infographic + `); + const actual = axe.commons.dom.isVisible(vNode.actualNode); assert.isTrue(actual); }); - it('should detect clip-path hidden text technique', function () { + it('should detect clip-path hidden text technique', () => { fixture.innerHTML = '
Hi
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); - it('should detect clip-path hidden text technique on parent', function () { - fixture.innerHTML = - '
' + - '
Hi
' + - '
'; + it('should detect clip-path hidden text technique on parent', () => { + fixture.innerHTML = html` +
+
Hi
+
+ `; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); - (shadowSupported ? it : xit)( - 'should correctly handle visible slotted elements', - function () { - function createContentSlotted() { - var group = document.createElement('div'); - group.innerHTML = '
Stuff
'; - return group; - } - function makeShadowTree(node) { - var root = node.attachShadow({ mode: 'open' }); - var div = document.createElement('div'); - root.appendChild(div); - div.appendChild(createContentSlotted()); - } - fixture.innerHTML = ''; - makeShadowTree(fixture.firstChild); - var tree = axe.utils.getFlattenedTree(fixture.firstChild); - var el = axe.utils.querySelectorAll(tree, 'a')[0]; - assert.isTrue(axe.commons.dom.isVisible(el.actualNode)); + it('should correctly handle visible slotted elements', () => { + function createContentSlotted() { + const group = document.createElement('div'); + group.innerHTML = '
Stuff
'; + return group; } - ); - (shadowSupported ? it : xit)( - 'should correctly handle hidden slotted elements', - function () { - function createContentSlotted() { - var group = document.createElement('div'); - group.innerHTML = - ''; - return group; - } - function makeShadowTree(node) { - var root = node.attachShadow({ mode: 'open' }); - var div = document.createElement('div'); - root.appendChild(div); - div.appendChild(createContentSlotted()); - } - fixture.innerHTML = ''; - makeShadowTree(fixture.firstChild); - var tree = axe.utils.getFlattenedTree(fixture.firstChild); - var el = axe.utils.querySelectorAll(tree, 'a')[0]; - assert.isFalse(axe.commons.dom.isVisible(el.actualNode)); + function makeShadowTree(node) { + const root = node.attachShadow({ mode: 'open' }); + const div = document.createElement('div'); + root.appendChild(div); + div.appendChild(createContentSlotted()); } - ); - it('should return false if element is visually hidden using position absolute, overflow hidden, and a very small height', function () { + fixture.innerHTML = ''; + makeShadowTree(fixture.firstChild); + const tree = axe.utils.getFlattenedTree(fixture.firstChild); + const el = axe.utils.querySelectorAll(tree, 'a')[0]; + assert.isTrue(axe.commons.dom.isVisible(el.actualNode)); + }); + it('should correctly handle hidden slotted elements', () => { + function createContentSlotted() { + const group = document.createElement('div'); + group.innerHTML = + ''; + return group; + } + function makeShadowTree(node) { + const root = node.attachShadow({ mode: 'open' }); + const div = document.createElement('div'); + root.appendChild(div); + div.appendChild(createContentSlotted()); + } + fixture.innerHTML = ''; + makeShadowTree(fixture.firstChild); + const tree = axe.utils.getFlattenedTree(fixture.firstChild); + const el = axe.utils.querySelectorAll(tree, 'a')[0]; + assert.isFalse(axe.commons.dom.isVisible(el.actualNode)); + }); + it('should return false if element is visually hidden using position absolute, overflow hidden, and a very small height', () => { fixture.innerHTML = '
StickySticky
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); }); - describe('screen readers', function () { + describe('screen readers', () => { // Firefox returns `null` if accessed inside a hidden iframe - it('should return false if computedStyle return null for whatever reason', function () { + it('should return false if computedStyle return null for whatever reason', () => { computedStyleStub = sinon.stub(window, 'getComputedStyle').returns(null); - var el = document.createElement('div'); + const el = document.createElement('div'); assert.isFalse(axe.commons.dom.isVisible(el, true)); }); - it('should return true on staticly-positioned, visible elements', function () { + it('should return true on staticly-positioned, visible elements', () => { fixture.innerHTML = '
Hello!
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should return true on absolutely positioned elements that are on-screen', function () { + it('should return true on absolutely positioned elements that are on-screen', () => { fixture.innerHTML = '
hi
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should respect position: fixed', function () { + it('should respect position: fixed', () => { fixture.innerHTML = '
StickySticky
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should properly calculate offsets according the offsetParent', function () { - fixture.innerHTML = - '
' + - '
Hi
' + - '
'; - var el = document.getElementById('target'); + it('should properly calculate offsets according the offsetParent', () => { + fixture.innerHTML = html` +
+
+ Hi +
+
+ `; + const el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should return true if moved offscreen with left', function () { + it('should return true if moved offscreen with left', () => { fixture.innerHTML = '
Hi
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should return true if moved offscreen with top', function () { + it('should return true if moved offscreen with top', () => { fixture.innerHTML = '
Hi
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should return true if moved offscreen with right', function () { + it('should return true if moved offscreen with right', () => { fixture.innerHTML = '
Hi
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should return true if moved offscreen with bottom', function () { + it('should return true if moved offscreen with bottom', () => { fixture.innerHTML = '
Hi
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should return true if text is moved offscreen with text-indent', function () { + it('should return true if text is moved offscreen with text-indent', () => { fixture.innerHTML = '
Hi
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should return false on detached elements', function () { - var el = document.createElement('div'); + it('should return false on detached elements', () => { + const el = document.createElement('div'); el.innerHTML = 'I am not visible because I am detached!'; assert.isFalse(axe.commons.dom.isVisible(el, true)); }); - it('should return true on a document', function () { + it('should return true on a document', () => { assert.isTrue(axe.commons.dom.isVisible(document, true)); }); - it('should return true if positioned staticly but top/left is set', function () { - fixture.innerHTML = - '
Hi
'; - var el = document.getElementById('target'); + it('should return true if positioned staticly but top/left is set', () => { + fixture.innerHTML = html` +
+ Hi +
+ `; + const el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should return false if `aria-hidden` is set', function () { + it('should return false if `aria-hidden` is set', () => { fixture.innerHTML = ''; - var el = document.getElementById('target'); + const el = document.getElementById('target'); assert.isFalse(axe.commons.dom.isVisible(el, true)); }); - it('should return false if `aria-hidden` is set on parent', function () { + it('should return false if `aria-hidden` is set on parent', () => { fixture.innerHTML = ''; - var el = document.getElementById('target'); + const el = document.getElementById('target'); assert.isFalse(axe.commons.dom.isVisible(el, true)); }); - it('should not calculate position on parents', function () { - fixture.innerHTML = - '
' + - '
Hi
' + - '
'; + it('should not calculate position on parents', () => { + fixture.innerHTML = html` +
+
+ Hi +
+
+ `; - var el = document.getElementById('target'); + const el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should know how `visibility` works', function () { - fixture.innerHTML = - '
' + - '
Hi
' + - '
'; + it('should know how `visibility` works', () => { + fixture.innerHTML = html` +
+
Hi
+
+ `; - var el = document.getElementById('target'); + const el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should detect clip rect hidden text technique', function () { - var el, - clip = - 'clip: rect(1px 1px 1px 1px);' + - 'clip: rect(1px, 1px, 1px, 1px);' + - 'width: 1px; height: 1px;' + - 'position: absolute;' + - 'overflow: hidden;'; + it('should detect clip rect hidden text technique', () => { + let el; + + const clip = ` + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + width: 1px; height: 1px; + position: absolute; + overflow: hidden; + `; - fixture.innerHTML = '
Hi
'; + fixture.innerHTML = html`
Hi
`; el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should detect even when clip is not applied because of positioning', function () { - var el, - clip = - 'clip: rect(1px 1px 1px 1px);' + - 'clip: rect(1px, 1px, 1px, 1px);' + - 'position: relative;' + - 'overflow: hidden;'; + it('should detect even when clip is not applied because of positioning', () => { + let el; - fixture.innerHTML = '
Hi
'; + const clip = ` + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + position: relative; + overflow: hidden; + `; + + fixture.innerHTML = html`
Hi
`; el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should detect clip rect hidden text technique on parent', function () { - var el, - clip = - 'clip: rect(1px 1px 1px 1px);' + - 'clip: rect(1px, 1px, 1px, 1px);' + - 'width: 1px; height: 1px;' + - 'position: absolute;' + - 'overflow: hidden;'; + it('should detect clip rect hidden text technique on parent', () => { + let el; - fixture.innerHTML = - '
' + '
Hi
' + '
'; + const clip = ` + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + width: 1px; height: 1px; + position: absolute; + overflow: hidden; + `; + + fixture.innerHTML = html`
+
Hi
+
`; el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should detect even when clip is not applied because of positioning on parent', function () { - var el, - clip = - 'clip: rect(1px 1px 1px 1px);' + - 'clip: rect(1px, 1px, 1px, 1px);' + - 'position: relative;' + - 'overflow: hidden;'; + it('should detect even when clip is not applied because of positioning on parent', () => { + let el; - fixture.innerHTML = - '
' + '
Hi
' + '
'; + const clip = ` + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + position: relative; + overflow: hidden; + `; + + fixture.innerHTML = html`
+
Hi
+
`; el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should detect poorly hidden clip rects', function () { - var el, - clip = - 'clip: rect(5px 1px 1px 5px);' + - 'clip: rect(5px, 1px, 1px, 5px);' + - 'width: 1px; height: 1px;' + - 'position: absolute;' + - 'overflow: hidden;'; + it('should detect poorly hidden clip rects', () => { + let el; - fixture.innerHTML = '
Hi
'; + const clip = ` + clip: rect(5px 1px 1px 5px); + clip: rect(5px, 1px, 1px, 5px); + width: 1px; height: 1px; + position: absolute; + overflow: hidden; + `; + + fixture.innerHTML = html`
Hi
`; el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should detect clip-path hidden text technique', function () { + it('should detect clip-path hidden text technique', () => { fixture.innerHTML = '
Hi
'; - var el = document.getElementById('target'); + const el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); - it('should detect clip-path hidden text technique on parent', function () { - fixture.innerHTML = - '
' + - '
Hi
' + - '
'; + it('should detect clip-path hidden text technique on parent', () => { + fixture.innerHTML = html` +
+
Hi
+
+ `; - var el = document.getElementById('target'); + const el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); }); - describe('SerialVirtualNode', function () { - it('should return true on statically-positioned, visible elements', function () { - var vNode = new axe.SerialVirtualNode({ + describe('SerialVirtualNode', () => { + it('should return true on statically-positioned, visible elements', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'div' }); assert.isTrue(axe.commons.dom.isVisible(vNode)); }); - it('should return false on STYLE tag', function () { - var vNode = new axe.SerialVirtualNode({ + it('should return false on STYLE tag', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'style' }); - var actual = axe.commons.dom.isVisible(vNode); + const actual = axe.commons.dom.isVisible(vNode); assert.isFalse(actual); }); - it('should return false on NOSCRIPT tag', function () { - var vNode = new axe.SerialVirtualNode({ + it('should return false on NOSCRIPT tag', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'noscript' }); - var actual = axe.commons.dom.isVisible(vNode); + const actual = axe.commons.dom.isVisible(vNode); assert.isFalse(actual); }); - it('should return false on TEMPLATE tag', function () { - var vNode = new axe.SerialVirtualNode({ + it('should return false on TEMPLATE tag', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'template' }); - var actual = axe.commons.dom.isVisible(vNode); + const actual = axe.commons.dom.isVisible(vNode); assert.isFalse(actual); }); - it('should not be affected by `aria-hidden`', function () { - var vNode = new axe.SerialVirtualNode({ + it('should not be affected by `aria-hidden`', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'div', attributes: { 'aria-hidden': true @@ -673,9 +780,9 @@ describe('dom.isVisible', function () { assert.isTrue(axe.commons.dom.isVisible(vNode)); }); - describe('screen readers', function () { - it('should return false if `aria-hidden` is set', function () { - var vNode = new axe.SerialVirtualNode({ + describe('screen readers', () => { + it('should return false if `aria-hidden` is set', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'div', attributes: { 'aria-hidden': true @@ -684,11 +791,11 @@ describe('dom.isVisible', function () { assert.isFalse(axe.commons.dom.isVisible(vNode, true)); }); - it('should return false if `aria-hidden` is set on parent', function () { - var vNode = new axe.SerialVirtualNode({ + it('should return false if `aria-hidden` is set on parent', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'div' }); - var parentVNode = new axe.SerialVirtualNode({ + const parentVNode = new axe.SerialVirtualNode({ nodeName: 'div', attributes: { 'aria-hidden': true diff --git a/test/commons/dom/is-visual-content.js b/test/commons/dom/is-visual-content.js index c1af93ac8..33961f19b 100644 --- a/test/commons/dom/is-visual-content.js +++ b/test/commons/dom/is-visual-content.js @@ -1,120 +1,118 @@ -describe('dom.isVisualContent', function () { - 'use strict'; +describe('dom.isVisualContent', () => { + const fixture = document.getElementById('fixture'); + const queryFixture = axe.testUtils.queryFixture; + const isVisualContent = axe.commons.dom.isVisualContent; - var fixture = document.getElementById('fixture'); - var queryFixture = axe.testUtils.queryFixture; - var isVisualContent = axe.commons.dom.isVisualContent; - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - describe('isVisualContent', function () { - it('should return true for img', function () { - var virtualNode = queryFixture(''); + describe('isVisualContent', () => { + it('should return true for img', () => { + const virtualNode = queryFixture(''); assert.isTrue(isVisualContent(virtualNode)); }); - it('should return true for iframe', function () { - var virtualNode = queryFixture(''); + it('should return true for iframe', () => { + const virtualNode = queryFixture(''); assert.isTrue(isVisualContent(virtualNode)); }); - it('should return true for object', function () { - var virtualNode = queryFixture(''); + it('should return true for object', () => { + const virtualNode = queryFixture(''); assert.isTrue(isVisualContent(virtualNode)); }); - it('should return true for video', function () { - var virtualNode = queryFixture(''); + it('should return true for video', () => { + const virtualNode = queryFixture(''); assert.isTrue(isVisualContent(virtualNode)); }); - it('should return true for audio', function () { - var virtualNode = queryFixture(''); + it('should return true for audio', () => { + const virtualNode = queryFixture(''); assert.isTrue(isVisualContent(virtualNode)); }); - it('should return true for canvas', function () { - var virtualNode = queryFixture(''); + it('should return true for canvas', () => { + const virtualNode = queryFixture(''); assert.isTrue(isVisualContent(virtualNode)); }); - it('should return true for svg', function () { - var virtualNode = queryFixture(''); + it('should return true for svg', () => { + const virtualNode = queryFixture(''); assert.isTrue(isVisualContent(virtualNode)); }); - it('should return true for math', function () { - var virtualNode = queryFixture(''); + it('should return true for math', () => { + const virtualNode = queryFixture(''); assert.isTrue(isVisualContent(virtualNode)); }); - it('should return true for button', function () { - var virtualNode = queryFixture(''); + it('should return true for button', () => { + const virtualNode = queryFixture(''); assert.isTrue(isVisualContent(virtualNode)); }); - it('should return true for select', function () { - var virtualNode = queryFixture(''); + it('should return true for select', () => { + const virtualNode = queryFixture(''); assert.isTrue(isVisualContent(virtualNode)); }); - it('should return true for textarea', function () { - var virtualNode = queryFixture(''); + it('should return true for textarea', () => { + const virtualNode = queryFixture(''); assert.isTrue(isVisualContent(virtualNode)); }); - it('should return true for keygen', function () { - var virtualNode = queryFixture(' { + const virtualNode = queryFixture(''); + it('should return true for progress', () => { + const virtualNode = queryFixture(''); assert.isTrue(isVisualContent(virtualNode)); }); - it('should return true for meter', function () { - var virtualNode = queryFixture(''); + it('should return true for meter', () => { + const virtualNode = queryFixture(''); assert.isTrue(isVisualContent(virtualNode)); }); - it('should return true for non-hidden input', function () { - var virtualNode = queryFixture(''); + it('should return true for non-hidden input', () => { + const virtualNode = queryFixture(''); assert.isTrue(isVisualContent(virtualNode)); }); - it('should return true for elements with a visual aria role', function () { - var virtualNode = queryFixture( - '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + it('should return true for elements with a visual aria role', () => { + const virtualNode = queryFixture( + `
` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `
` ); for (var i = 0; i < virtualNode.children.length; i++) { assert.isTrue( isVisualContent(virtualNode.children[i]), - 'for role ' + virtualNode.children[i].attr('role') + `for role ${virtualNode.children[i].attr('role')}` ); } }); - it('should return false for hidden input', function () { - var virtualNode = queryFixture(''); + it('should return false for hidden input', () => { + const virtualNode = queryFixture(''); assert.isFalse(isVisualContent(virtualNode)); }); - it('should return false for p', function () { - var virtualNode = queryFixture('

Paragraph!

'); + it('should return false for p', () => { + const virtualNode = queryFixture('

Paragraph!

'); assert.isFalse(isVisualContent(virtualNode)); }); }); diff --git a/test/commons/dom/shadow-elements-from-point.js b/test/commons/dom/shadow-elements-from-point.js index 75b1a26ca..bcb2d70b2 100644 --- a/test/commons/dom/shadow-elements-from-point.js +++ b/test/commons/dom/shadow-elements-from-point.js @@ -1,58 +1,55 @@ -describe('dom.shadowElementsFromPoint', function () { - 'use strict'; +describe('dom.shadowElementsFromPoint', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var shadowSupported = axe.testUtils.shadowSupport.v1; + const fixture = document.getElementById('fixture'); - afterEach(function () { + afterEach(() => { document.getElementById('fixture').innerHTML = ''; }); - (shadowSupported ? it : xit)( - 'should return an array from inside and outside of shadow dom', - function () { - fixture.innerHTML = - '
'; - var container = fixture.querySelector('#container'); - var shadow1 = container.attachShadow({ mode: 'open' }); - shadow1.innerHTML = - '

Text

' + - '
'; - var paragraph = shadow1.querySelector('p'); - var container2 = shadow1.querySelector('#shadowHost2'); - var shadow2 = container2.attachShadow({ mode: 'open' }); - shadow2.innerHTML = 'Text'; - var shadowSpan = shadow2.querySelector('span'); - axe.testUtils.flatTreeSetup(fixture); + it('should return an array from inside and outside of shadow dom', () => { + fixture.innerHTML = + '
'; + const container = fixture.querySelector('#container'); + const shadow1 = container.attachShadow({ mode: 'open' }); + shadow1.innerHTML = html` +

Text

+
+ `; + const paragraph = shadow1.querySelector('p'); + const container2 = shadow1.querySelector('#shadowHost2'); + const shadow2 = container2.attachShadow({ mode: 'open' }); + shadow2.innerHTML = 'Text'; + const shadowSpan = shadow2.querySelector('span'); + axe.testUtils.flatTreeSetup(fixture); - container.scrollIntoView(); + container.scrollIntoView(); - var spanCoords = shadowSpan.getBoundingClientRect(); - var result = axe.commons.dom.shadowElementsFromPoint( - spanCoords.x, - spanCoords.y - ); - var pCoords = paragraph.getBoundingClientRect(); - var result2 = axe.commons.dom.shadowElementsFromPoint( - pCoords.x, - pCoords.y - ); + const spanCoords = shadowSpan.getBoundingClientRect(); + const result = axe.commons.dom.shadowElementsFromPoint( + spanCoords.x, + spanCoords.y + ); + const pCoords = paragraph.getBoundingClientRect(); + const result2 = axe.commons.dom.shadowElementsFromPoint( + pCoords.x, + pCoords.y + ); - assert.includeMembers(result, [shadowSpan, container2]); - assert.notInclude(result, paragraph); - assert.includeMembers(result2, [paragraph, container]); - assert.notInclude(result2, shadowSpan); - } - ); + assert.includeMembers(result, [shadowSpan, container2]); + assert.notInclude(result, paragraph); + assert.includeMembers(result2, [paragraph, container]); + assert.notInclude(result2, shadowSpan); + }); - it('does not throw when elementsFromPoints returns null', function () { - var mockDocument = { - elementsFromPoint: function () { + it('does not throw when elementsFromPoints returns null', () => { + const mockDocument = { + elementsFromPoint: () => { return null; } }; - var out; - assert.doesNotThrow(function () { + let out; + assert.doesNotThrow(() => { out = axe.commons.dom.shadowElementsFromPoint(10, 10, mockDocument); }); assert.deepEqual(out, []); diff --git a/test/commons/dom/url-props-from-attribute.js b/test/commons/dom/url-props-from-attribute.js index b86f396de..42cbd3ad2 100644 --- a/test/commons/dom/url-props-from-attribute.js +++ b/test/commons/dom/url-props-from-attribute.js @@ -1,38 +1,38 @@ -describe('dom.urlPropsFromAttribute', function () { - 'use strict'; +describe('dom.urlPropsFromAttribute', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var queryFixture = axe.testUtils.queryFixture; + const fixture = document.getElementById('fixture'); + const queryFixture = axe.testUtils.queryFixture; - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('returns undefined when given node does not have specified attribute', function () { - var vNode = queryFixture( + it('returns undefined when given node does not have specified attribute', () => { + const vNode = queryFixture( '' ); - var actual = axe.commons.dom.urlPropsFromAttribute( + const actual = axe.commons.dom.urlPropsFromAttribute( vNode.actualNode, 'href' ); assert.isUndefined(actual); }); - it('returns undefined when `A` has no `HREF` attribute', function () { - var vNode = queryFixture('Follow us on Instagram'); - var actual = axe.commons.dom.urlPropsFromAttribute( + it('returns undefined when `A` has no `HREF` attribute', () => { + const vNode = queryFixture('Follow us on Instagram'); + const actual = axe.commons.dom.urlPropsFromAttribute( vNode.actualNode, 'href' ); assert.isUndefined(actual); }); - it('returns URL properties when `A` with `HREF` (has port)', function () { - var vNode = queryFixture( + it('returns URL properties when `A` with `HREF` (has port)', () => { + const vNode = queryFixture( 'Follow us on Instagram' ); - var expected = { + const expected = { filename: 'playground.html', hash: '', hostname: 'localhost', @@ -41,16 +41,16 @@ describe('dom.urlPropsFromAttribute', function () { protocol: 'http:', search: {} }; - var actual = axe.commons.dom.urlPropsFromAttribute( + const actual = axe.commons.dom.urlPropsFromAttribute( vNode.actualNode, 'href' ); assert.deepEqual(actual, expected); }); - it('returns URL properties when `A` with empty `HREF`', function () { - var vNode = queryFixture('See commons tests'); - var actual = axe.commons.dom.urlPropsFromAttribute( + it('returns URL properties when `A` with empty `HREF`', () => { + const vNode = queryFixture('See commons tests'); + const actual = axe.commons.dom.urlPropsFromAttribute( vNode.actualNode, 'href' ); @@ -59,11 +59,11 @@ describe('dom.urlPropsFromAttribute', function () { assert.equal(actual.port, location.port); }); - it('returns URL properties for `A` with `HREF` (having HTTPS protocol)', function () { - var vNode = queryFixture( + it('returns URL properties for `A` with `HREF` (having HTTPS protocol)', () => { + const vNode = queryFixture( 'follow us on Facebook' ); - var expected = { + const expected = { filename: '', hash: '', hostname: 'facebook.com', @@ -72,18 +72,18 @@ describe('dom.urlPropsFromAttribute', function () { protocol: 'http:', search: {} }; - var actual = axe.commons.dom.urlPropsFromAttribute( + const actual = axe.commons.dom.urlPropsFromAttribute( vNode.actualNode, 'href' ); assert.deepEqual(actual, expected); }); - it('returns URL properties for `A` with `HREF` (having FTP protocol)', function () { - var vNode = queryFixture( + it('returns URL properties for `A` with `HREF` (having FTP protocol)', () => { + const vNode = queryFixture( 'Navigate to My Website' ); - var expected = { + const expected = { filename: '', hash: '', hostname: 'mywebsite.org', @@ -92,18 +92,18 @@ describe('dom.urlPropsFromAttribute', function () { protocol: 'ftp:', search: {} }; - var actual = axe.commons.dom.urlPropsFromAttribute( + const actual = axe.commons.dom.urlPropsFromAttribute( vNode.actualNode, 'href' ); assert.deepEqual(actual, expected); }); - it('returns URL properties for `A` with `HREF` which has subdirectory and inline link', function () { - var vNode = queryFixture( + it('returns URL properties for `A` with `HREF` which has subdirectory and inline link', () => { + const vNode = queryFixture( 'Go to Issues' ); - var expected = { + const expected = { filename: '', hash: '', hostname: 'mysite.com', @@ -112,18 +112,18 @@ describe('dom.urlPropsFromAttribute', function () { protocol: 'http:', search: {} }; - var actual = axe.commons.dom.urlPropsFromAttribute( + const actual = axe.commons.dom.urlPropsFromAttribute( vNode.actualNode, 'href' ); assert.deepEqual(actual, expected); }); - it('returns URL properties for `A` with `HREF` which has subdirectory and hashbang', function () { - var vNode = queryFixture( + it('returns URL properties for `A` with `HREF` which has subdirectory and hashbang', () => { + const vNode = queryFixture( 'See our services' ); - var expected = { + const expected = { filename: '', hash: '#!foo', hostname: 'mysite.com', @@ -132,18 +132,18 @@ describe('dom.urlPropsFromAttribute', function () { protocol: 'http:', search: {} }; - var actual = axe.commons.dom.urlPropsFromAttribute( + const actual = axe.commons.dom.urlPropsFromAttribute( vNode.actualNode, 'href' ); assert.deepEqual(actual, expected); }); - it('returns URL properties for `A` with `HREF` which has search query', function () { - var vNode = queryFixture( + it('returns URL properties for `A` with `HREF` which has search query', () => { + const vNode = queryFixture( 'Get list of foo bars' ); - var expected = { + const expected = { filename: '', hash: '', hostname: 'mysite.com', @@ -154,18 +154,18 @@ describe('dom.urlPropsFromAttribute', function () { q: 'foo' } }; - var actual = axe.commons.dom.urlPropsFromAttribute( + const actual = axe.commons.dom.urlPropsFromAttribute( vNode.actualNode, 'href' ); assert.deepEqual(actual, expected); }); - it('returns URL properties for `A` with `HREF` which has multiple search query parameters', function () { - var vNode = queryFixture( + it('returns URL properties for `A` with `HREF` which has multiple search query parameters', () => { + const vNode = queryFixture( 'Get list of foo bars' ); - var expected = { + const expected = { filename: '', hash: '', hostname: 'mysite.com', @@ -181,18 +181,18 @@ describe('dom.urlPropsFromAttribute', function () { version: '1.2.3' } }; - var actual = axe.commons.dom.urlPropsFromAttribute( + const actual = axe.commons.dom.urlPropsFromAttribute( vNode.actualNode, 'href' ); assert.deepEqual(actual, expected); }); - it('returns URL properties for `A` with `HREF` which has filename', function () { - var vNode = queryFixture( + it('returns URL properties for `A` with `HREF` which has filename', () => { + const vNode = queryFixture( 'Book tour' ); - var expected = { + const expected = { filename: 'calendar.html', hash: '', hostname: 'mysite.com', @@ -201,18 +201,18 @@ describe('dom.urlPropsFromAttribute', function () { protocol: 'http:', search: {} }; - var actual = axe.commons.dom.urlPropsFromAttribute( + const actual = axe.commons.dom.urlPropsFromAttribute( vNode.actualNode, 'href' ); assert.deepEqual(actual, expected); }); - it('returns URL properties for `A` with `HREF` which has filename as `index` (ignores index.*)', function () { - var vNode = queryFixture( + it('returns URL properties for `A` with `HREF` which has filename as `index` (ignores index.*)', () => { + const vNode = queryFixture( 'Book tour' ); - var expected = { + const expected = { filename: '', hash: '', hostname: 'mysite.com', @@ -221,20 +221,30 @@ describe('dom.urlPropsFromAttribute', function () { protocol: 'http:', search: {} }; - var actual = axe.commons.dom.urlPropsFromAttribute( + const actual = axe.commons.dom.urlPropsFromAttribute( vNode.actualNode, 'href' ); assert.deepEqual(actual, expected); }); - it('returns URL properties for `A` with `HREF` that is contained in SVG document', function () { - var vNode = queryFixture( - '' + - '' + - '' - ); - var expected = { + it('returns URL properties for `A` with `HREF` that is contained in SVG document', () => { + const vNode = queryFixture(html` + + + + + + `); + const expected = { filename: 'calendar.html', hash: '', hostname: 'mysite.com', @@ -243,7 +253,7 @@ describe('dom.urlPropsFromAttribute', function () { protocol: 'http:', search: {} }; - var actual = axe.commons.dom.urlPropsFromAttribute( + const actual = axe.commons.dom.urlPropsFromAttribute( vNode.actualNode, 'href' ); diff --git a/test/commons/dom/visibility-methods.js b/test/commons/dom/visibility-methods.js index 867b50f69..900eb79de 100644 --- a/test/commons/dom/visibility-methods.js +++ b/test/commons/dom/visibility-methods.js @@ -1,4 +1,5 @@ describe('dom.visibility-methods', () => { + const html = axe.testUtils.html; const { queryFixture, shadowCheckSetup } = axe.testUtils; const { nativelyHidden, @@ -32,7 +33,7 @@ describe('dom.visibility-methods', () => { }) .forEach(nodeName => { const vNode = new axe.VirtualNode(document.createElement(nodeName)); - assert.isFalse(nativelyHidden(vNode), nodeName + ' is not visible'); + assert.isFalse(nativelyHidden(vNode), `${nodeName} is not visible`); }); }); }); @@ -58,12 +59,12 @@ describe('dom.visibility-methods', () => { }); it('should return false for area element', () => { - const vNode = queryFixture( - '' + - '' + - '' + - '' - ); + const vNode = queryFixture(html` + + + + + `); assert.isFalse(displayHidden(vNode)); }); }); @@ -205,101 +206,210 @@ describe('dom.visibility-methods', () => { }); }); - describe('overflowHidden', function () { - it('should return true for element with "overflow:hidden" and small width', function () { - var vNode = queryFixture( + describe('overflowHidden', () => { + it('should return true for element with "overflow:hidden" and small width', () => { + const vNode = queryFixture( '
Hello world
' ); assert.isTrue(overflowHidden(vNode)); }); - it('should return true for element with "overflow:hidden" and small height', function () { - var vNode = queryFixture( + it('should return true for element with "overflow:hidden" and small height', () => { + const vNode = queryFixture( '
Hello world
' ); assert.isTrue(overflowHidden(vNode)); }); - it('should return true for parent with "overflow: hidden" and element outside parent rect', function () { - var vNode = queryFixture( - '
' + - '
Hello world
' + - '
' - ); + it('should return true for parent with "overflow: hidden" and element outside parent rect', () => { + const vNode = queryFixture(html` +
+
Hello world
+
+ `); assert.isTrue(overflowHidden(vNode)); }); - it('should return true for ancestor with "overflow: hidden" and element outside ancestor rect', function () { - var vNode = queryFixture( - '
' + - '
' + - '
' + - '
Hello world
' + - '
' + - '
' + - '
' - ); + it('should return true for ancestor with "overflow: hidden" and element outside ancestor rect', () => { + const vNode = queryFixture(html` +
+
+
+
Hello world
+
+
+
+ `); assert.isTrue(overflowHidden(vNode)); }); - it('should return true for multiple ancestors with "overflow: hidden" and element outside one ancestor rect', function () { - var vNode = queryFixture( - '
' + - '
' + - '
' + - '
Hello world
' + - '
' + - '
' + - '
' - ); + it('should return true for multiple ancestors with "overflow: hidden" and element outside one ancestor rect', () => { + const vNode = queryFixture(html` +
+
+
+
Hello world
+
+
+
+ `); assert.isTrue(overflowHidden(vNode)); }); it('should return true for element barely inside "overflow: hidden" parent using floating point', () => { - var vNode = queryFixture( - '
' + - '
Hello world
' + - '
' - ); + const vNode = queryFixture(html` +
+
Hello world
+
+ `); assert.isTrue(overflowHidden(vNode)); }); - it('should return false for element with "overflow:hidden" and width larger than 1px', function () { - var vNode = queryFixture( + it('should return false for element with "overflow:hidden" and width larger than 1px', () => { + const vNode = queryFixture( '
Hello world
' ); assert.isFalse(overflowHidden(vNode)); }); - it('should return false for element with "overflow:hidden" and height larger than 1px', function () { - var vNode = queryFixture( + it('should return false for element with "overflow:hidden" and height larger than 1px', () => { + const vNode = queryFixture( '
Hello world
' ); assert.isFalse(overflowHidden(vNode)); }); - it('should return false for element with just "overflow:hidden"', function () { - var vNode = queryFixture( + it('should return false for element with just "overflow:hidden"', () => { + const vNode = queryFixture( '
Hello world
' ); assert.isFalse(overflowHidden(vNode)); }); - it('should return false for element without "overflow:hidden"', function () { - var vNode = queryFixture('
Hello world
'); + it('should return false for element without "overflow:hidden"', () => { + const vNode = queryFixture('
Hello world
'); assert.isFalse(overflowHidden(vNode)); }); - it('should return false for multiple ancestors with "overflow: hidden" and element is inside all ancestor rects', function () { - var vNode = queryFixture( - '
' + - '
' + - '
' + - '
Hello world
' + - '
' + - '
' + - '
' - ); + it('should return false for multiple ancestors with "overflow: hidden" and element is inside all ancestor rects', () => { + const vNode = queryFixture(html` +
+
+
+
Hello world
+
+
+
+ `); + assert.isFalse(overflowHidden(vNode)); + }); + + it('should return false for ancestor with "overflow:hidden" and element with "position:fixed"', () => { + const vNode = queryFixture(html` +
+
+ Hello world +
+
+ `); + assert.isFalse(overflowHidden(vNode)); + }); + + it('should return false for ancestor with "overflow:hidden; position:relative" and element with "position:fixed"', () => { + const vNode = queryFixture(html` +
+
+ Hello world +
+
+ `); + assert.isFalse(overflowHidden(vNode)); + }); + + it('should return false for ancestor with "overflow:hidden" and element with "position:absolute"', () => { + const vNode = queryFixture(html` +
+
+ Hello world +
+
+ `); + assert.isFalse(overflowHidden(vNode)); + }); + + it('should return true for ancestor with "overflow:hidden; position:relative" and element with "position:absolute"', () => { + const vNode = queryFixture(html` +
+
+ Hello world +
+
+ `); + assert.isTrue(overflowHidden(vNode)); + }); + + it('should return true for ancestor with "overflow:hidden" and element with "position:absolute" if ancestor in-between uses "position:relative"', () => { + const vNode = queryFixture(html` +
+
+
+ Hello world +
+
+
+ `); + assert.isTrue(overflowHidden(vNode)); + }); + + it('should return true for ancestor with "overflow:hidden" and element with "position:absolute" if ancestor in-between uses "position:sticky"', () => { + const vNode = queryFixture(html` +
+
+
+ Hello world +
+
+
+ `); + assert.isTrue(overflowHidden(vNode)); + }); + + it('should return false for ancestor with "overflow:hidden" and element with "position:absolute" if ancestor in-between uses "position:absolute"', () => { + const vNode = queryFixture(html` +
+
+
+ Hello world +
+
+
+ `); + assert.isFalse(overflowHidden(vNode)); + }); + + it('should return false for ancestor with "overflow:hidden" and element with "position:absolute" if ancestor in-between uses "position:fixed"', () => { + const vNode = queryFixture(html` +
+
+
+ Hello world +
+
+
+ `); + assert.isFalse(overflowHidden(vNode)); + }); + + it('should return false for ancestor with "overflow:hidden" and element with "position:absolute" if ancestor of overflow node uses position other than static', () => { + const vNode = queryFixture(html` +
+
+
+ Hello world +
+
+
+ `); assert.isFalse(overflowHidden(vNode)); }); }); @@ -437,7 +547,7 @@ describe('dom.visibility-methods', () => { describe('detailsHidden', () => { it('should return true for closed details parent', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World

Hidden

@@ -448,7 +558,7 @@ describe('dom.visibility-methods', () => { }); it('should return false for closed details', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World

Hidden

@@ -459,7 +569,7 @@ describe('dom.visibility-methods', () => { }); it('should return false for summary in closed details', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World

Hidden

@@ -470,7 +580,7 @@ describe('dom.visibility-methods', () => { }); it('should return false for summary in open details', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World

Hidden

@@ -481,7 +591,7 @@ describe('dom.visibility-methods', () => { }); it('should return false for open details parent', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World

Hidden

@@ -492,7 +602,7 @@ describe('dom.visibility-methods', () => { }); it('should return true for not first summary in closed details', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World Not a summary @@ -504,7 +614,7 @@ describe('dom.visibility-methods', () => { }); it('should return false for element not in details', () => { - const vNode = queryFixture(` + const vNode = queryFixture(html`
Hello World
diff --git a/test/commons/dom/visually-contains.js b/test/commons/dom/visually-contains.js index b63dea9d5..453dafffe 100644 --- a/test/commons/dom/visually-contains.js +++ b/test/commons/dom/visually-contains.js @@ -1,21 +1,22 @@ -describe('dom.visuallyContains', function () { - 'use strict'; +describe('dom.visuallyContains', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var queryFixture = axe.testUtils.queryFixture; + const fixture = document.getElementById('fixture'); + const queryFixture = axe.testUtils.queryFixture; - var shadowSupported = axe.testUtils.shadowSupport.v1; - - afterEach(function () { + afterEach(() => { document.getElementById('fixture').innerHTML = ''; }); - it('should return true when element is trivially contained', function () { - var target = queryFixture( - '
' + - '
' + - '
' - ); + it('should return true when element is trivially contained', () => { + const target = queryFixture(html` +
+
+
+ `); assert.isTrue( axe.commons.dom.visuallyContains( target.actualNode, @@ -24,38 +25,53 @@ describe('dom.visuallyContains', function () { ); }); - it('should return false when overflow is hidden', function () { - var target = queryFixture( - '
' + - '
' + - '
' - ); - var result = axe.commons.dom.visuallyContains( + it('should return false when overflow is hidden', () => { + const target = queryFixture(html` +
+
+
+ `); + const result = axe.commons.dom.visuallyContains( target.actualNode, target.parent.actualNode ); assert.isFalse(result); }); - it('should return false when absolutely positioned content does not overlap', function () { - var target = queryFixture( - '
' + - '
Text' + - '
' - ); - var result = axe.commons.dom.visuallyContains( + it('should return false when absolutely positioned content does not overlap', () => { + const target = queryFixture(html` +
+
+ Text +
+
+ `); + const result = axe.commons.dom.visuallyContains( target.actualNode, target.parent.actualNode ); assert.isFalse(result); }); - it('should return false when element is outside of margin', function () { - var target = queryFixture( - '
' + - '
' + - '
' - ); + it('should return false when element is outside of margin', () => { + const target = queryFixture(html` +
+
+
+ `); assert.isFalse( axe.commons.dom.visuallyContains( target.actualNode, @@ -64,12 +80,17 @@ describe('dom.visuallyContains', function () { ); }); - it('should return false when overflow is visible', function () { - var target = queryFixture( - '
' + - '
' + - '
' - ); + it('should return false when overflow is visible', () => { + const target = queryFixture(html` +
+
+
+ `); assert.isFalse( axe.commons.dom.visuallyContains( target.actualNode, @@ -78,12 +99,17 @@ describe('dom.visuallyContains', function () { ); }); - it('should return true when element is scrollable', function () { - var target = queryFixture( - '
' + - '
' + - '
' - ); + it('should return true when element is scrollable', () => { + const target = queryFixture(html` +
+
+
+ `); assert.isTrue( axe.commons.dom.visuallyContains( target.actualNode, @@ -92,11 +118,11 @@ describe('dom.visuallyContains', function () { ); }); - it('should return true when element is inline', function () { + it('should return true when element is inline', () => { // result depends on the display property of the element - var target = queryFixture( - '' - ); + const target = queryFixture(html` + + `); assert.isTrue( axe.commons.dom.visuallyContains( target.actualNode, @@ -105,147 +131,161 @@ describe('dom.visuallyContains', function () { ); }); - it('should return false when element is partially contained', function () { - var target = queryFixture( - '
' + - '

Text
more text

' - ); - var result = axe.commons.dom.visuallyContains( + it('should return false when element is partially contained', () => { + const target = queryFixture(html` +
+

+ Text
more text +

+
+ `); + const result = axe.commons.dom.visuallyContains( target.actualNode, target.parent.actualNode ); assert.isFalse(result); }); - it('should return true when element is contained by scroll region', function () { - var target = queryFixture( - '
' + - '
' + - '
' + - '

Text

' - ); - var parent = fixture.querySelector('#parent'); - var result = axe.commons.dom.visuallyContains(target.actualNode, parent); + it('should return true when element is contained by scroll region', () => { + const target = queryFixture(html` +
+
+
+

Text

+
+
+ `); + const parent = fixture.querySelector('#parent'); + const result = axe.commons.dom.visuallyContains(target.actualNode, parent); assert.isTrue(result); }); - it('should return true for child with truncated text', function () { - var target = queryFixture( - '

' + - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et sollicitudin quam. Fuscemi odio, egestas pulvinar erat eget, vehicula tempus est. Proin vitae ullamcorper velit. Donec sagittis est justo, mattis iaculis arcu facilisis id. Proin pulvinar ornare arcu a fermentum. Quisque et dignissim nulla,sit amet consectetur ipsum. Donec in libero porttitor, dapibus neque imperdiet, aliquam est. Vivamus blandit volutpat fringilla. In mi magna, mollis sit amet imperdiet eu, rutrum ut tellus. Mauris vel condimentum nibh, quis ultricies nisi. Vivamus accumsan quam mauris, id iaculis quam fringilla ac. Curabitur pulvinar dolor ac magna vehicula, non auctor ligula dignissim. Nam ac nibh porttitor, malesuada tortor varius, feugiat turpis. Mauris dapibus, tellus ut viverra porta, ipsum turpis bibendum ligula, at tempor felis ante non libero.' + - '

' - ); - var result = axe.commons.dom.visuallyContains( + it('should return true for child with truncated text', () => { + const target = queryFixture(html` +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et + sollicitudin quam. Fuscemi odio, egestas pulvinar erat eget, vehicula + tempus est. Proin vitae ullamcorper velit. Donec sagittis est justo, + mattis iaculis arcu facilisis id. Proin pulvinar ornare arcu a + fermentum. Quisque et dignissim nulla,sit amet consectetur ipsum. + Donec in libero porttitor, dapibus neque imperdiet, aliquam est. + Vivamus blandit volutpat fringilla. In mi magna, mollis sit amet + imperdiet eu, rutrum ut tellus. Mauris vel condimentum nibh, quis + ultricies nisi. Vivamus accumsan quam mauris, id iaculis quam + fringilla ac. Curabitur pulvinar dolor ac magna vehicula, non auctor + ligula dignissim. Nam ac nibh porttitor, malesuada tortor varius, + feugiat turpis. Mauris dapibus, tellus ut viverra porta, ipsum turpis + bibendum ligula, at tempor felis ante non libero. +

+ `); + const result = axe.commons.dom.visuallyContains( target.actualNode, target.parent.actualNode ); assert.isTrue(result); }); - it('should return false if element is outside overflow hidden', function () { - var target = queryFixture( - '
' + - '
Some text
' + - '
' - ); + it('should return false if element is outside overflow hidden', () => { + const target = queryFixture(html` +
+
Some text
+
+ `); - var parent = fixture.querySelector('#parent'); - var result = axe.commons.dom.visuallyContains(target.actualNode, parent); + const parent = fixture.querySelector('#parent'); + const result = axe.commons.dom.visuallyContains(target.actualNode, parent); assert.isFalse(result); }); - it('should allow subpixel contains due to rounding', function () { - var target = queryFixture( - '
' + - '
Some text
' + - '
' - ); + it('should allow subpixel contains due to rounding', () => { + const target = queryFixture(html` +
+
+ Some text +
+
+ `); - var parent = fixture.querySelector('#parent'); - var result = axe.commons.dom.visuallyContains(target.actualNode, parent); + const parent = fixture.querySelector('#parent'); + const result = axe.commons.dom.visuallyContains(target.actualNode, parent); assert.isTrue(result); }); - (shadowSupported ? it : xit)( - 'should return true when element is visually contained across shadow boundary', - function () { - fixture.innerHTML = - '
'; - var container = fixture.querySelector('#container'); - var shadow = container.attachShadow({ mode: 'open' }); - shadow.innerHTML = - '
'; - axe.testUtils.flatTreeSetup(fixture); - var target = shadow.querySelector('#target'); - var result = axe.commons.dom.visuallyContains(target, container); - assert.isTrue(result); - } - ); - - (shadowSupported ? it : xit)( - 'should return true when element is contained by scroll region across shadow boundary', - function () { - fixture.innerHTML = - '
' + - '
' + - '
' + - '
'; - var container = fixture.querySelector('#container'); - var shadow = container.attachShadow({ mode: 'open' }); - shadow.innerHTML = '

Text

'; - axe.testUtils.flatTreeSetup(fixture); - var target = shadow.querySelector('#target'); - var parent = fixture.querySelector('#parent'); - var result = axe.commons.dom.visuallyContains(target, parent); - assert.isTrue(result); - } - ); - - (shadowSupported ? it : xit)( - 'should return false when element is not visually contained across shadow boundary', - function () { - fixture.innerHTML = - '
'; - var container = fixture.querySelector('#container'); - var shadow = container.attachShadow({ mode: 'open' }); - shadow.innerHTML = - '
'; - axe.testUtils.flatTreeSetup(fixture); - var target = shadow.querySelector('#target'); - var result = axe.commons.dom.visuallyContains(target, container); - assert.isFalse(result); - } - ); - - (shadowSupported ? it : xit)( - 'should return false when element is absolutely positioned off of background across shadow boundary', - function () { - fixture.innerHTML = - '
'; - var container = fixture.querySelector('#container'); - var shadow = container.attachShadow({ mode: 'open' }); - shadow.innerHTML = - '
Text
'; - axe.testUtils.flatTreeSetup(fixture); - var target = shadow.querySelector('#shadowTarget'); - var result = axe.commons.dom.visuallyContains(target, container); - assert.isFalse(result); - } - ); - - (shadowSupported ? it : xit)( - 'should return false when element is partially positioned off of background across shadow boundary', - function () { - fixture.innerHTML = - '
'; - var container = fixture.querySelector('#container'); - var shadow = container.attachShadow({ mode: 'open' }); - shadow.innerHTML = - '
Text
'; - axe.testUtils.flatTreeSetup(fixture); - var target = shadow.querySelector('#shadowTarget'); - var result = axe.commons.dom.visuallyContains(target, container); - assert.isFalse(result); - } - ); + it('should return true when element is visually contained across shadow boundary', () => { + fixture.innerHTML = + '
'; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); + shadow.innerHTML = + '
'; + axe.testUtils.flatTreeSetup(fixture); + const target = shadow.querySelector('#target'); + const result = axe.commons.dom.visuallyContains(target, container); + assert.isTrue(result); + }); + + it('should return true when element is contained by scroll region across shadow boundary', () => { + fixture.innerHTML = html` +
+
+
+
+
+
+ `; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); + shadow.innerHTML = '

Text

'; + axe.testUtils.flatTreeSetup(fixture); + const target = shadow.querySelector('#target'); + const parent = fixture.querySelector('#parent'); + const result = axe.commons.dom.visuallyContains(target, parent); + assert.isTrue(result); + }); + + it('should return false when element is not visually contained across shadow boundary', () => { + fixture.innerHTML = + '
'; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); + shadow.innerHTML = + '
'; + axe.testUtils.flatTreeSetup(fixture); + const target = shadow.querySelector('#target'); + const result = axe.commons.dom.visuallyContains(target, container); + assert.isFalse(result); + }); + + it('should return false when element is absolutely positioned off of background across shadow boundary', () => { + fixture.innerHTML = + '
'; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); + shadow.innerHTML = + '
Text
'; + axe.testUtils.flatTreeSetup(fixture); + const target = shadow.querySelector('#shadowTarget'); + const result = axe.commons.dom.visuallyContains(target, container); + assert.isFalse(result); + }); + + it('should return false when element is partially positioned off of background across shadow boundary', () => { + fixture.innerHTML = + '
'; + const container = fixture.querySelector('#container'); + const shadow = container.attachShadow({ mode: 'open' }); + shadow.innerHTML = + '
Text
'; + axe.testUtils.flatTreeSetup(fixture); + const target = shadow.querySelector('#shadowTarget'); + const result = axe.commons.dom.visuallyContains(target, container); + assert.isFalse(result); + }); }); diff --git a/test/commons/dom/visually-overlaps.js b/test/commons/dom/visually-overlaps.js index 69fe6d379..bed8be90b 100644 --- a/test/commons/dom/visually-overlaps.js +++ b/test/commons/dom/visually-overlaps.js @@ -1,72 +1,100 @@ -describe('dom.visuallyOverlaps', function () { - 'use strict'; +describe('dom.visuallyOverlaps', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); + const fixture = document.getElementById('fixture'); - afterEach(function () { + afterEach(() => { document.getElementById('fixture').innerHTML = ''; }); - it('should return true when element is trivially contained', function () { - fixture.innerHTML = - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var targetRect = target.getBoundingClientRect(); + it('should return true when element is trivially contained', () => { + fixture.innerHTML = html` +
+
+
+ `; + const target = fixture.querySelector('#target'); + const targetRect = target.getBoundingClientRect(); assert.isTrue( axe.commons.dom.visuallyOverlaps(targetRect, target.parentNode) ); }); - it('should return false when rect has no overlap', function () { - fixture.innerHTML = - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var targetRect = target.getBoundingClientRect(); + it('should return false when rect has no overlap', () => { + fixture.innerHTML = html` +
+
+
+ `; + const target = fixture.querySelector('#target'); + const targetRect = target.getBoundingClientRect(); assert.isFalse( axe.commons.dom.visuallyOverlaps(targetRect, target.parentNode) ); }); - it('should return true when rect has overlap', function () { - fixture.innerHTML = - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var targetRect = target.getBoundingClientRect(); + it('should return true when rect has overlap', () => { + fixture.innerHTML = html` +
+
+
+ `; + const target = fixture.querySelector('#target'); + const targetRect = target.getBoundingClientRect(); assert.isTrue( axe.commons.dom.visuallyOverlaps(targetRect, target.parentNode) ); }); - it('should return true when container is scrollable and rect is in the scroll area', function () { - fixture.innerHTML = - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var targetRect = target.getBoundingClientRect(); + it('should return true when container is scrollable and rect is in the scroll area', () => { + fixture.innerHTML = html` +
+
+
+ `; + const target = fixture.querySelector('#target'); + const targetRect = target.getBoundingClientRect(); assert.isTrue( axe.commons.dom.visuallyOverlaps(targetRect, target.parentNode) ); }); - it('should return false when container has overflow hidden and rect is in the scroll area', function () { - fixture.innerHTML = - '
' + - '
' + - '
'; - var target = fixture.querySelector('#target'); - var targetRect = target.getBoundingClientRect(); + it('should return false when container has overflow hidden and rect is in the scroll area', () => { + fixture.innerHTML = html` +
+
+
+ `; + const target = fixture.querySelector('#target'); + const targetRect = target.getBoundingClientRect(); assert.isFalse( axe.commons.dom.visuallyOverlaps(targetRect, target.parentNode) ); diff --git a/test/commons/dom/visually-sort.js b/test/commons/dom/visually-sort.js index 897f61421..6057ed37c 100644 --- a/test/commons/dom/visually-sort.js +++ b/test/commons/dom/visually-sort.js @@ -1,6 +1,6 @@ // This method is mostly tested through color-contrast integrations describe('visually-sort', () => { - 'use strict'; + const html = axe.testUtils.html; const fixture = document.querySelector('#fixture'); const visuallySort = axe.commons.dom.visuallySort; @@ -8,13 +8,22 @@ describe('visually-sort', () => { let root; beforeEach(() => { - fixture.innerHTML = ` -
+ fixture.innerHTML = html` +
-
+
-
+
Text
diff --git a/test/commons/forms/is-aria-combobox.js b/test/commons/forms/is-aria-combobox.js index 3e19c6733..20e203661 100644 --- a/test/commons/forms/is-aria-combobox.js +++ b/test/commons/forms/is-aria-combobox.js @@ -1,23 +1,22 @@ -describe('forms.isAriaCombobox', function () { - 'use strict'; - var isAriaCombobox = axe.commons.forms.isAriaCombobox; - var flatTreeSetup = axe.testUtils.flatTreeSetup; +describe('forms.isAriaCombobox', () => { + const isAriaCombobox = axe.commons.forms.isAriaCombobox; + const flatTreeSetup = axe.testUtils.flatTreeSetup; - it('returns true for an element with role=combobox', function () { - var node = document.createElement('div'); + it('returns true for an element with role=combobox', () => { + const node = document.createElement('div'); node.setAttribute('role', 'combobox'); flatTreeSetup(node); assert.isTrue(isAriaCombobox(node)); }); - it('returns false for elements without role', function () { - var node = document.createElement('div'); + it('returns false for elements without role', () => { + const node = document.createElement('div'); flatTreeSetup(node); assert.isFalse(isAriaCombobox(node)); }); - it('returns false for elements with incorrect role', function () { - var node = document.createElement('div'); + it('returns false for elements with incorrect role', () => { + const node = document.createElement('div'); node.setAttribute('role', 'main'); flatTreeSetup(node); assert.isFalse(isAriaCombobox(node)); diff --git a/test/commons/forms/is-aria-listbox.js b/test/commons/forms/is-aria-listbox.js index 8ce82fafe..d71a21f44 100644 --- a/test/commons/forms/is-aria-listbox.js +++ b/test/commons/forms/is-aria-listbox.js @@ -1,30 +1,29 @@ -describe('forms.isAriaListbox', function () { - 'use strict'; - var isAriaListbox = axe.commons.forms.isAriaListbox; - var flatTreeSetup = axe.testUtils.flatTreeSetup; +describe('forms.isAriaListbox', () => { + const isAriaListbox = axe.commons.forms.isAriaListbox; + const flatTreeSetup = axe.testUtils.flatTreeSetup; - it('returns true for an element with role=listbox', function () { - var node = document.createElement('div'); + it('returns true for an element with role=listbox', () => { + const node = document.createElement('div'); node.setAttribute('role', 'listbox'); flatTreeSetup(node); assert.isTrue(isAriaListbox(node)); }); - it('returns false for elements without role', function () { - var node = document.createElement('div'); + it('returns false for elements without role', () => { + const node = document.createElement('div'); flatTreeSetup(node); assert.isFalse(isAriaListbox(node)); }); - it('returns false for elements with incorrect role', function () { - var node = document.createElement('div'); + it('returns false for elements with incorrect role', () => { + const node = document.createElement('div'); node.setAttribute('role', 'main'); flatTreeSetup(node); assert.isFalse(isAriaListbox(node)); }); - it('returns false for native select', function () { - var node = document.createElement('select'); + it('returns false for native select', () => { + const node = document.createElement('select'); flatTreeSetup(node); assert.isFalse(isAriaListbox(node)); }); diff --git a/test/commons/forms/is-aria-range.js b/test/commons/forms/is-aria-range.js index c41a5b43b..6f0ebff34 100644 --- a/test/commons/forms/is-aria-range.js +++ b/test/commons/forms/is-aria-range.js @@ -1,37 +1,36 @@ -describe('forms.isAriaRange', function () { - 'use strict'; - var isAriaRange = axe.commons.forms.isAriaRange; - var flatTreeSetup = axe.testUtils.flatTreeSetup; +describe('forms.isAriaRange', () => { + const isAriaRange = axe.commons.forms.isAriaRange; + const flatTreeSetup = axe.testUtils.flatTreeSetup; - it('returns true for an element with range roles', function () { - var rangeRoles = ['progressbar', 'scrollbar', 'slider', 'spinbutton']; - rangeRoles.forEach(function (role) { - var node = document.createElement('div'); + it('returns true for an element with range roles', () => { + const rangeRoles = ['progressbar', 'scrollbar', 'slider', 'spinbutton']; + rangeRoles.forEach(role => { + const node = document.createElement('div'); node.setAttribute('role', role); node.setAttribute('aria-valuenow', '0'); flatTreeSetup(node); assert.isTrue( isAriaRange(node), - 'role="' + role + '" is not an aria range role' + `role="${role}" is not an aria range role` ); }); }); - it('returns false for elements without role', function () { - var node = document.createElement('div'); + it('returns false for elements without role', () => { + const node = document.createElement('div'); flatTreeSetup(node); assert.isFalse(isAriaRange(node)); }); - it('returns false for elements with incorrect role', function () { - var node = document.createElement('div'); + it('returns false for elements with incorrect role', () => { + const node = document.createElement('div'); node.setAttribute('role', 'main'); flatTreeSetup(node); assert.isFalse(isAriaRange(node)); }); - it('returns false for native range elements', function () { - var nativeRangeElements = [ + it('returns false for native range elements', () => { + const nativeRangeElements = [ { nodeName: 'progress' }, @@ -44,15 +43,15 @@ describe('forms.isAriaRange', function () { type: 'number' } ]; - nativeRangeElements.forEach(function (elm) { - var node = document.createElement(elm.nodeName); + nativeRangeElements.forEach(elm => { + const node = document.createElement(elm.nodeName); if (elm.type) { node.setAttribute('type', elm.type); } flatTreeSetup(node); assert.isFalse( isAriaRange(node), - node.outterHTML + ' is not an aria range element' + `${node.outterHTML} is not an aria range element` ); }); }); diff --git a/test/commons/forms/is-aria-textbox.js b/test/commons/forms/is-aria-textbox.js index 70bb6a2ce..ad8104167 100644 --- a/test/commons/forms/is-aria-textbox.js +++ b/test/commons/forms/is-aria-textbox.js @@ -1,30 +1,29 @@ -describe('forms.isAriaTextbox', function () { - 'use strict'; - var isAriaTextbox = axe.commons.forms.isAriaTextbox; - var flatTreeSetup = axe.testUtils.flatTreeSetup; +describe('forms.isAriaTextbox', () => { + const isAriaTextbox = axe.commons.forms.isAriaTextbox; + const flatTreeSetup = axe.testUtils.flatTreeSetup; - it('returns true for an element with role=textbox', function () { - var node = document.createElement('div'); + it('returns true for an element with role=textbox', () => { + const node = document.createElement('div'); node.setAttribute('role', 'textbox'); flatTreeSetup(node); assert.isTrue(isAriaTextbox(node)); }); - it('returns false for elements without role', function () { - var node = document.createElement('div'); + it('returns false for elements without role', () => { + const node = document.createElement('div'); flatTreeSetup(node); assert.isFalse(isAriaTextbox(node)); }); - it('returns false for elements with incorrect role', function () { - var node = document.createElement('div'); + it('returns false for elements with incorrect role', () => { + const node = document.createElement('div'); node.setAttribute('role', 'main'); flatTreeSetup(node); assert.isFalse(isAriaTextbox(node)); }); - it('returns false for native textbox inputs', function () { - var node = document.createElement('input'); + it('returns false for native textbox inputs', () => { + const node = document.createElement('input'); node.setAttribute('type', 'text'); flatTreeSetup(node); assert.isFalse(isAriaTextbox(node)); diff --git a/test/commons/forms/is-disabled.js b/test/commons/forms/is-disabled.js index 1dbbe7d52..5910f0a60 100644 --- a/test/commons/forms/is-disabled.js +++ b/test/commons/forms/is-disabled.js @@ -1,123 +1,123 @@ -describe('forms.isDisabled', function () { - 'use strict'; - var isDisabled = axe.commons.forms.isDisabled; - var fixtureSetup = axe.testUtils.fixtureSetup; - var fixture = document.getElementById('fixture'); - var shadowSupport = axe.testUtils.shadowSupport; - - afterEach(function () { +describe('forms.isDisabled', () => { + const html = axe.testUtils.html; + const isDisabled = axe.commons.forms.isDisabled; + const fixtureSetup = axe.testUtils.fixtureSetup; + const fixture = document.getElementById('fixture'); + + afterEach(() => { fixture.innerHTML = ''; }); - describe('with disabled attr', function () { - it('returns false when not set', function () { + describe('with disabled attr', () => { + it('returns false when not set', () => { fixtureSetup(''); - var node = axe.utils.querySelectorAll(axe._tree[0], 'input')[0]; + const node = axe.utils.querySelectorAll(axe._tree[0], 'input')[0]; assert.isFalse(isDisabled(node)); }); - it('returns true for an element that can be disabled', function () { + it('returns true for an element that can be disabled', () => { fixtureSetup(''); - var node = axe.utils.querySelectorAll(axe._tree[0], 'input')[0]; + const node = axe.utils.querySelectorAll(axe._tree[0], 'input')[0]; assert.isTrue(isDisabled(node)); }); - it('returns false for an element that can not be disabled', function () { + it('returns false for an element that can not be disabled', () => { fixtureSetup('Hello'); - var node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; + const node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; assert.isFalse(isDisabled(node)); }); - it('returns true when disabled has a value', function () { + it('returns true when disabled has a value', () => { fixtureSetup(''); - var node = axe.utils.querySelectorAll(axe._tree[0], 'input')[0]; + const node = axe.utils.querySelectorAll(axe._tree[0], 'input')[0]; assert.isTrue(isDisabled(node)); }); - it('returns true when in a disabled fieldset', function () { - fixtureSetup( - '
' + 'Hello world' + '
' - ); - var node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; + it('returns true when in a disabled fieldset', () => { + fixtureSetup(html` +
+ Hello world +
+ `); + const node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; assert.isTrue(isDisabled(node)); }); - it('returns true when in a disabled button', function () { - fixtureSetup( - '' - ); - var node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; + it('returns true when in a disabled button', () => { + fixtureSetup(html` + + `); + const node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; assert.isTrue(isDisabled(node)); }); - (shadowSupport.v1 ? it : xit)( - 'returns true for an ancestor in the flat tree that can be disabled', - function () { - fixture.innerHTML = '
'; - var shadowRoot = fixture - .querySelector('section') - .attachShadow({ mode: 'open' }); - shadowRoot.innerHTML = ''; - axe._tree = axe.utils.getFlattenedTree(fixture); - - var node = axe.utils.querySelectorAll(axe._tree[0], 'input')[0]; - assert.isTrue(isDisabled(node)); - } - ); + it('returns true for an ancestor in the flat tree that can be disabled', () => { + fixture.innerHTML = '
'; + const shadowRoot = fixture + .querySelector('section') + .attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = ''; + axe._tree = axe.utils.getFlattenedTree(fixture); + + const node = axe.utils.querySelectorAll(axe._tree[0], 'input')[0]; + assert.isTrue(isDisabled(node)); + }); }); - describe('with aria-disabled attr', function () { - it('returns true for an element with aria-disabled=true', function () { + describe('with aria-disabled attr', () => { + it('returns true for an element with aria-disabled=true', () => { fixtureSetup('hello'); - var node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; + const node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; assert.isTrue(isDisabled(node)); }); - it('returns false for an element when aria-disabled is not true', function () { + it('returns false for an element when aria-disabled is not true', () => { fixtureSetup('hello'); - var node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; + const node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; assert.isFalse(isDisabled(node)); }); - it('returns true if the closest ancestor with aria-disabled is set to true', function () { - fixtureSetup( - '
' + - '
hello
' + - '
' - ); - var node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; + it('returns true if the closest ancestor with aria-disabled is set to true', () => { + fixtureSetup(html` +
+
hello
+
+ `); + const node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; assert.isTrue(isDisabled(node)); }); - it('returns false if the closest ancestor with aria-disabled is set to false', function () { - fixtureSetup( - '
' + - '
hello
' + - '
' - ); - var node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; + it('returns false if the closest ancestor with aria-disabled is set to false', () => { + fixtureSetup(html` +
+
hello
+
+ `); + const node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; assert.isFalse(isDisabled(node)); }); }); - describe('with both disabled and aria-disabled', function () { - it('returns true when aria-disabled=false and disabled=disabled', function () { - fixtureSetup( - '
' + - 'hello' + - '
' - ); - var node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; + describe('with both disabled and aria-disabled', () => { + it('returns true when aria-disabled=false and disabled=disabled', () => { + fixtureSetup(html` +
+ hello +
+ `); + const node = axe.utils.querySelectorAll(axe._tree[0], 'span')[0]; assert.isTrue(isDisabled(node)); }); diff --git a/test/commons/forms/is-native-select.js b/test/commons/forms/is-native-select.js index 069298a56..663b4a503 100644 --- a/test/commons/forms/is-native-select.js +++ b/test/commons/forms/is-native-select.js @@ -1,22 +1,19 @@ -describe('forms.isNativeSelect', function () { - 'use strict'; - var isNativeSelect = axe.commons.forms.isNativeSelect; - var queryFixture = axe.testUtils.queryFixture; +describe('forms.isNativeSelect', () => { + const isNativeSelect = axe.commons.forms.isNativeSelect; + const queryFixture = axe.testUtils.queryFixture; - it('returns true for a select element', function () { - var node = queryFixture(''); + it('returns true for a select element', () => { + const node = queryFixture(''); assert.isTrue(isNativeSelect(node)); }); - it('returns false for non-select elements', function () { - var nonSelectElements = ['a', 'h1', 'div', 'span', 'main']; - nonSelectElements.forEach(function (nodeName) { - var node = queryFixture( - '<' + nodeName + ' id="target">' - ); + it('returns false for non-select elements', () => { + const nonSelectElements = ['a', 'h1', 'div', 'span', 'main']; + nonSelectElements.forEach(nodeName => { + const node = queryFixture(`<${nodeName} id="target">`); assert.isFalse( isNativeSelect(node), - '<' + nodeName + '> is not a native select element' + `<${nodeName}> is not a native select element` ); }); }); diff --git a/test/commons/forms/is-native-textbox.js b/test/commons/forms/is-native-textbox.js index d5a67686e..8e94e3c09 100644 --- a/test/commons/forms/is-native-textbox.js +++ b/test/commons/forms/is-native-textbox.js @@ -1,10 +1,10 @@ -describe('forms.isNativeTextbox', function () { - 'use strict'; - var isNativeTextbox = axe.commons.forms.isNativeTextbox; - var queryFixture = axe.testUtils.queryFixture; +describe('forms.isNativeTextbox', () => { + const html = axe.testUtils.html; + const isNativeTextbox = axe.commons.forms.isNativeTextbox; + const queryFixture = axe.testUtils.queryFixture; - it('returns true for a text inputs', function () { - var textInputs = [ + it('returns true for a text inputs', () => { + const textInputs = [ 'date', 'datetime', 'datetime-local', @@ -19,22 +19,22 @@ describe('forms.isNativeTextbox', function () { 'url', 'week' ]; - textInputs.forEach(function (type) { - var node = queryFixture(''); + textInputs.forEach(type => { + const node = queryFixture(html``); assert.isTrue( isNativeTextbox(node), - ' is a native text input' + html` is a native text input` ); }); }); - it('returns true for a textarea element', function () { - var node = queryFixture('`; + fixture.innerHTML = html``; axe.testUtils.flatTreeSetup(fixture); const target = axe.utils.querySelectorAll(axe._tree, 'textarea')[0]; @@ -1305,7 +1294,8 @@ describe('text.accessibleTextVirtual', () => { }); it('should find an explicit label', () => { - fixture.innerHTML = ``; + fixture.innerHTML = html``; axe.testUtils.flatTreeSetup(fixture); const target = axe.utils.querySelectorAll(axe._tree, 'textarea')[0]; @@ -1579,7 +1569,7 @@ describe('text.accessibleTextVirtual', () => { ]; it('should find aria-labelledby', () => { - tags.forEach(function (tag) { + tags.forEach(tag => { fixture.innerHTML = html`
Hello
World
@@ -1599,7 +1589,7 @@ describe('text.accessibleTextVirtual', () => { }); it('should find aria-label', () => { - tags.forEach(function (tag) { + tags.forEach(tag => { const elm = document.createElement(tag); elm.setAttribute('aria-label', 'Hello World'); elm.style.display = 'inline'; // Firefox hack, see above @@ -1613,7 +1603,7 @@ describe('text.accessibleTextVirtual', () => { }); it('should find a title attribute', () => { - tags.forEach(function (tag) { + tags.forEach(tag => { const elm = document.createElement(tag); elm.setAttribute('title', 'Hello World'); elm.style.display = 'inline'; // Firefox hack, see above @@ -1627,7 +1617,7 @@ describe('text.accessibleTextVirtual', () => { }); it('should otherwise be empty string', () => { - tags.forEach(function (tag) { + tags.forEach(tag => { fixture.innerHTML = `<${tag}>`; axe.testUtils.flatTreeSetup(fixture); @@ -1649,8 +1639,48 @@ describe('text.accessibleTextVirtual', () => { }); }); + describe('natively hidden elements', () => { + const tags = ['style', 'script', 'noscript', 'template']; + + it('should not use content as accessible name when directly referenced by aria-labelledby', () => { + tags.forEach(tag => { + fixture.innerHTML = html`
<${tag} id="el-id">content`; + axe.testUtils.flatTreeSetup(fixture); + const target = axe.utils.querySelectorAll(axe._tree, '#t1')[0]; + assert.equal(axe.commons.text.accessibleTextVirtual(target), '', tag); + }); + }); + + it('should not use content when inside a hidden container referenced by aria-labelledby', () => { + tags.forEach(tag => { + fixture.innerHTML = html` +
+ +`; + axe.testUtils.flatTreeSetup(fixture); + const target = axe.utils.querySelectorAll(axe._tree, '#t1')[0]; + assert.equal( + axe.commons.text.accessibleTextVirtual(target), + 'Expected label', + tag + ); + }); + }); + + it('should ignore aria-label as accessible name when directly referenced by aria-labelledby', () => { + tags.forEach(tag => { + fixture.innerHTML = html`
<${tag} id="el-id" aria-label="aria-label">`; + axe.testUtils.flatTreeSetup(fixture); + const target = axe.utils.querySelectorAll(axe._tree, '#t1')[0]; + assert.equal(axe.commons.text.accessibleTextVirtual(target), '', tag); + }); + }); + }); + describe('text.accessibleText acceptance tests', () => { - 'use strict'; // Tests borrowed from the AccName 1.1 testing docs // https://www.w3.org/wiki/AccName_1.1_Testable_Statements#Name_test_case_539 diff --git a/test/commons/text/form-control-value.js b/test/commons/text/form-control-value.js index 96552ec35..e31b502c9 100644 --- a/test/commons/text/form-control-value.js +++ b/test/commons/text/form-control-value.js @@ -91,19 +91,22 @@ describe('text.formControlValue', () => { }; fixtureSetup( Object.keys(formData).reduce((code, fieldType) => { - return `${code}`; + return html`${code}`; }, '') ); axe.utils .querySelectorAll(axe._tree[0], '#fixture input') - .forEach(function (target) { + .forEach(target => { const expected = formData[getNodeType(target.actualNode)]; assert.isDefined(expected); const actual = nativeTextboxValue(target); assert.equal( actual, expected, - 'Expected value for ' + target.actualNode.outerHTML + `Expected value for ${target.actualNode.outerHTML}` ); }); }); @@ -123,7 +126,7 @@ describe('text.formControlValue', () => { `); axe.utils .querySelectorAll(axe._tree[0], '#fixture input') - .forEach(function (target) { + .forEach(target => { // Safari and IE11 do not support the color input type // and thus treat them as text inputs. ignore fallback // inputs @@ -134,7 +137,7 @@ describe('text.formControlValue', () => { assert.equal( nativeTextboxValue(target), '', - 'Expected no value for ' + target.actualNode.outerHTML + `Expected no value for ${target.actualNode.outerHTML}` ); }); }); @@ -148,7 +151,7 @@ describe('text.formControlValue', () => { it('returns `` for other elements', () => { // some random elements: ['div', 'span', 'h1', 'output', 'summary', 'style', 'template'].forEach( - function (nodeName) { + nodeName => { const target = document.createElement(nodeName); target.value = 'foo'; // That shouldn't do anything fixture.appendChild(target); @@ -232,7 +235,7 @@ describe('text.formControlValue', () => { it('returns `` for other elements', () => { // some random elements: ['div', 'span', 'h1', 'output', 'summary', 'style', 'template'].forEach( - function (nodeName) { + nodeName => { const target = document.createElement(nodeName); target.value = 'foo'; // That shouldn't do anything fixture.appendChild(target); @@ -454,7 +457,7 @@ describe('text.formControlValue', () => { it('returns `` for elements without role=combobox', () => { const target = queryFixture( - `
${comboboxContent}
`, + html`
${comboboxContent}
`, '[role=listbox]' ); assert.equal(ariaComboboxValue(target), ''); @@ -469,8 +472,8 @@ describe('text.formControlValue', () => { it('passes aria-owned listbox to `ariaListboxValue` and returns its result', () => { const target = queryFixture( - '
' + - comboboxContent + html`
+ ${comboboxContent}` ); assert.equal(ariaComboboxValue(target), 'bar'); }); @@ -486,8 +489,8 @@ describe('text.formControlValue', () => { assert.equal(ariaRangeValue(target), ''); }); - rangeRoles.forEach(function (role) { - describe('with ' + role, () => { + rangeRoles.forEach(role => { + describe(`with ${role}`, () => { it('returns the result of aria-valuenow', () => { const target = queryFixture(html`
foo
diff --git a/test/commons/text/is-human-interpretable.js b/test/commons/text/is-human-interpretable.js index 5a4c022a8..c7e1f3d75 100644 --- a/test/commons/text/is-human-interpretable.js +++ b/test/commons/text/is-human-interpretable.js @@ -1,63 +1,76 @@ -describe('text.isHumanInterpretable', function () { - it('returns 0 when given string is empty', function () { - var actual = axe.commons.text.isHumanInterpretable(''); +describe('text.isHumanInterpretable', () => { + it('returns 0 when given string is empty', () => { + const actual = axe.commons.text.isHumanInterpretable(''); assert.equal(actual, 0); }); - it('returns 0 when given string is a single character that is blacklisted as icon', function () { - var blacklistedIcons = ['x', 'i']; - blacklistedIcons.forEach(function (iconText) { - var actual = axe.commons.text.isHumanInterpretable(iconText); + it('returns 0 when given string is a single alpha character', () => { + const singleCharacterExamples = ['i', 'x', 'X', '×', '']; + singleCharacterExamples.forEach(characterExample => { + const actual = axe.commons.text.isHumanInterpretable(characterExample); assert.equal(actual, 0); }); }); - it('returns 0 when given string is only punctuations', function () { - var actual = axe.commons.text.isHumanInterpretable('?!!!,.'); + it('returns 0 when given string is in the symbolic text characters set (blocklist)', () => { + const blocklistedSymbols = ['aA', 'Aa', 'abc', 'ABC']; + blocklistedSymbols.forEach(symbolicText => { + const actual = axe.commons.text.isHumanInterpretable(symbolicText); + assert.equal(actual, 0); + }); + }); + + it('returns 0 when given string is only punctuations', () => { + const actual = axe.commons.text.isHumanInterpretable('?!!!,.'); assert.equal(actual, 0); }); - it('returns 1 when given string has emoji as a part of the sentence', function () { - var actual = axe.commons.text.isHumanInterpretable('I like 🏀'); + it('returns 1 when given string that has a number', () => { + const actual = axe.commons.text.isHumanInterpretable('7'); + assert.equal(actual, 1); + }); + + it('returns 1 when given string has emoji as a part of the sentence', () => { + const actual = axe.commons.text.isHumanInterpretable('I like 🏀'); assert.equal(actual, 1); }); - it('returns 1 when given string has non BMP character (eg: windings font) as part of the sentence', function () { - var actual = axe.commons.text.isHumanInterpretable('I ✂ my hair'); + it('returns 1 when given string has non BMP character (eg: windings font) as part of the sentence', () => { + const actual = axe.commons.text.isHumanInterpretable('I ✂ my hair'); assert.equal(actual, 1); }); - it('returns 1 when given string has both non BMP character, and emoji as part of the sentence', function () { - var actual = axe.commons.text.isHumanInterpretable( + it('returns 1 when given string has both non BMP character, and emoji as part of the sentence', () => { + const actual = axe.commons.text.isHumanInterpretable( 'I ✂ my hair, and I like 🏀' ); assert.equal(actual, 1); }); - it('returns 0 when given string has only emoji', function () { - var actual = axe.commons.text.isHumanInterpretable('🏀🍔🍉🎅'); + it('returns 0 when given string has only emoji', () => { + const actual = axe.commons.text.isHumanInterpretable('🏀🍔🍉🎅'); assert.equal(actual, 0); }); - it('returns 0 when given string has only non BNP characters', function () { - var actual = axe.commons.text.isHumanInterpretable('⌛👓'); + it('returns 0 when given string has only non BNP characters', () => { + const actual = axe.commons.text.isHumanInterpretable('⌛👓'); assert.equal(actual, 0); }); - it('returns 0 when given string has combination of only non BNP characters and emojis', function () { - var actual = axe.commons.text.isHumanInterpretable('⌛👓🏀🍔🍉🎅'); + it('returns 0 when given string has combination of only non BNP characters and emojis', () => { + const actual = axe.commons.text.isHumanInterpretable('⌛👓🏀🍔🍉🎅'); assert.equal(actual, 0); }); - it('returns 1 when given string is a punctuated sentence', function () { - var actual = axe.commons.text.isHumanInterpretable( + it('returns 1 when given string is a punctuated sentence', () => { + const actual = axe.commons.text.isHumanInterpretable( "I like football, but I prefer basketball; although I can't play either very well." ); assert.equal(actual, 1); }); - it('returns 1 for a sentence without emoji or punctuations', function () { - var actual = axe.commons.text.isHumanInterpretable('Earth is round'); + it('returns 1 for a sentence without emoji or punctuations', () => { + const actual = axe.commons.text.isHumanInterpretable('Earth is round'); assert.equal(actual, 1); }); }); diff --git a/test/commons/text/is-icon-ligature.js b/test/commons/text/is-icon-ligature.js index 2da2ba8ba..3e1d93263 100644 --- a/test/commons/text/is-icon-ligature.js +++ b/test/commons/text/is-icon-ligature.js @@ -1,6 +1,4 @@ describe('text.isIconLigature', () => { - 'use strict'; - const isIconLigature = axe.commons.text.isIconLigature; const queryFixture = axe.testUtils.queryFixture; const fontApiSupport = !!document.fonts; diff --git a/test/commons/text/is-valid-autocomplete.js b/test/commons/text/is-valid-autocomplete.js index e9f4a4ddc..d32a846ad 100644 --- a/test/commons/text/is-valid-autocomplete.js +++ b/test/commons/text/is-valid-autocomplete.js @@ -206,4 +206,14 @@ describe('text.isValidAutocomplete', () => { ); }); }); + + describe('options.ignoredValues', () => { + it('returns undefined if value is invalid and ignored', () => { + assert.isUndefined( + isValidAutocomplete('bad-term', { + ignoredValues: ['bad-term'] + }) + ); + }); + }); }); diff --git a/test/commons/text/label-text.js b/test/commons/text/label-text.js index 2ee27154a..b0de912b0 100644 --- a/test/commons/text/label-text.js +++ b/test/commons/text/label-text.js @@ -1,118 +1,106 @@ -describe('text.labelText', function () { - var labelText = axe.commons.text.labelText; - var queryFixture = axe.testUtils.queryFixture; +describe('text.labelText', () => { + const html = axe.testUtils.html; + const labelText = axe.commons.text.labelText; + const queryFixture = axe.testUtils.queryFixture; - it('returns the text of an implicit label', function () { - var target = queryFixture( - '' - ); - assert.equal(labelText(target), 'My implicit label'); + it('returns the text of an implicit label', () => { + const target = queryFixture(html` + + `); + assert.equal(labelText(target), ' My implicit label '); }); - it('returns the text of an explicit label', function () { - var target = queryFixture( - '' + '' - ); + it('returns the text of an explicit label', () => { + const target = queryFixture(html` + + `); assert.equal(labelText(target), 'My explicit label'); }); - it('ignores the text of nested implicit labels', function () { - var target = queryFixture( - '' + it('ignores the text of nested implicit labels', () => { + const target = queryFixture( + html`` ); assert.equal(labelText(target), 'My inner label'); }); - it('concatinates multiple explicit labels', function () { - var target = queryFixture( - '' + - '' + - '' + it('concatinates multiple explicit labels', () => { + const target = queryFixture( + html`` ); assert.equal(labelText(target), 'My label 1 My label 2'); }); - it('concatinates explicit and implicit labels', function () { - var target = queryFixture( - '' + - '' + it('concatinates explicit and implicit labels', () => { + const target = queryFixture( + html`` ); assert.equal(labelText(target), 'My explicit label My implicit label'); }); - it('returns label text in the DOM order', function () { - var target = queryFixture( - '' + - '' + - '' + it('returns label text in the DOM order', () => { + const target = queryFixture( + html`` ); assert.equal(labelText(target), 'Label 1 My implicit Label 2 Label 3'); }); - it('does not return the same label twice', function () { - var target = queryFixture( - '' + it('does not return the same label twice', () => { + const target = queryFixture( + html`` ); assert.equal(labelText(target), 'My implicit and explicit label'); }); - it('ignores the value of a textbox', function () { - var target = queryFixture( - '' + it('ignores the value of a textbox', () => { + const target = queryFixture( + html`` ); assert.equal(labelText(target), 'My label'); }); - it('ignores the content of a textarea', function () { - var target = queryFixture( - '` ); assert.equal(labelText(target), 'My label'); }); - it('ignores the options of a select element', function () { - var target = queryFixture( - '' + it('ignores the options of a select element', () => { + const target = queryFixture( + html`` ); assert.equal(labelText(target), 'My label'); }); - describe('with context = { inControlContext: true }', function () { - it('returns `` ', function () { - var target = queryFixture( - '' + - '' + describe('with context = { inControlContext: true }', () => { + it('returns `` ', () => { + const target = queryFixture( + html`` ); assert.equal(labelText(target, { inControlContext: true }), ''); }); }); - describe('with context = { inLabelledByContext: true }', function () { - it('returns `` ', function () { - var target = queryFixture( - '' + - '' + describe('with context = { inLabelledByContext: true }', () => { + it('returns `` ', () => { + const target = queryFixture( + html`` ); assert.equal(labelText(target, { inLabelledByContext: true }), ''); }); diff --git a/test/commons/text/label-virtual.js b/test/commons/text/label-virtual.js index f7b3ec814..1674dba89 100644 --- a/test/commons/text/label-virtual.js +++ b/test/commons/text/label-virtual.js @@ -1,167 +1,196 @@ -describe('text.labelVirtual', function () { - 'use strict'; +describe('text.labelVirtual', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - afterEach(function () { + const fixture = document.getElementById('fixture'); + afterEach(() => { fixture.innerHTML = ''; }); - it('is called from text.label', function () { - fixture.innerHTML = - '
monkeys
bananas
' + - ''; + it('is called from text.label', () => { + fixture.innerHTML = html` +
monkeys
+
bananas
+ + `; axe.testUtils.flatTreeSetup(document.body); - var target = fixture.querySelector('#target'); + const target = fixture.querySelector('#target'); assert.equal(axe.commons.text.label(target), 'monkeys bananas'); }); - describe('aria-labelledby', function () { - it('should join text with a single space', function () { - fixture.innerHTML = - '
monkeys
bananas
' + - ''; + describe('aria-labelledby', () => { + it('should join text with a single space', () => { + fixture.innerHTML = html` +
monkeys
+
bananas
+ + `; - var tree = axe.testUtils.flatTreeSetup(document.body); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const tree = axe.testUtils.flatTreeSetup(document.body); + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.equal(axe.commons.text.labelVirtual(target), 'monkeys bananas'); }); - it('should filter invisible elements', function () { - fixture.innerHTML = - '
monkeys
' + - ''; + it('should filter invisible elements', () => { + fixture.innerHTML = html` +
monkeys
+ + + `; - var tree = axe.testUtils.flatTreeSetup(document.body); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const tree = axe.testUtils.flatTreeSetup(document.body); + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.equal(axe.commons.text.labelVirtual(target), 'monkeys'); }); - it('should take precedence over aria-label', function () { - fixture.innerHTML = - '
monkeys
bananas
' + - ''; - - var tree = axe.testUtils.flatTreeSetup(document.body); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + it('should take precedence over aria-label', () => { + fixture.innerHTML = html` +
monkeys
+
bananas
+ + `; + + const tree = axe.testUtils.flatTreeSetup(document.body); + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.equal(axe.commons.text.labelVirtual(target), 'monkeys bananas'); }); - it('should take precedence over explicit labels', function () { - fixture.innerHTML = - '
monkeys
bananas
' + - '' + - ''; + it('should take precedence over explicit labels', () => { + fixture.innerHTML = html` +
monkeys
+
bananas
+ + + `; - var tree = axe.testUtils.flatTreeSetup(document.body); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const tree = axe.testUtils.flatTreeSetup(document.body); + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.equal(axe.commons.text.labelVirtual(target), 'monkeys bananas'); }); - it('should take precedence over implicit labels', function () { - fixture.innerHTML = - '
monkeys
bananas
' + - ''; - - var tree = axe.testUtils.flatTreeSetup(document.body); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + it('should take precedence over implicit labels', () => { + fixture.innerHTML = html` +
monkeys
+
bananas
+ + `; + + const tree = axe.testUtils.flatTreeSetup(document.body); + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.equal(axe.commons.text.labelVirtual(target), 'monkeys bananas'); }); - it('should ignore whitespace only labels', function () { - fixture.innerHTML = - '
\n
' + - ''; + it('should ignore whitespace only labels', () => { + fixture.innerHTML = html` +
+
+ + `; - var tree = axe.testUtils.flatTreeSetup(document.body); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const tree = axe.testUtils.flatTreeSetup(document.body); + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isNull(axe.commons.text.labelVirtual(target)); }); }); - describe('aria-label', function () { - it('should detect it', function () { + describe('aria-label', () => { + it('should detect it', () => { fixture.innerHTML = ''; - var tree = axe.testUtils.flatTreeSetup(document.body); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const tree = axe.testUtils.flatTreeSetup(document.body); + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.equal(axe.commons.text.labelVirtual(target), 'monkeys'); }); - it('should ignore whitespace only labels', function () { + it('should ignore whitespace only labels', () => { fixture.innerHTML = ''; - var tree = axe.testUtils.flatTreeSetup(document.body); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const tree = axe.testUtils.flatTreeSetup(document.body); + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isNull(axe.commons.text.labelVirtual(target)); }); - it('should take precedence over explicit labels', function () { - fixture.innerHTML = - '' + - ''; + it('should take precedence over explicit labels', () => { + fixture.innerHTML = html` + + + `; - var tree = axe.testUtils.flatTreeSetup(document.body); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const tree = axe.testUtils.flatTreeSetup(document.body); + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.equal(axe.commons.text.labelVirtual(target), 'monkeys'); }); - it('should take precedence over implicit labels', function () { - fixture.innerHTML = - ''; + it('should take precedence over implicit labels', () => { + fixture.innerHTML = html` + + `; - var tree = axe.testUtils.flatTreeSetup(document.body); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const tree = axe.testUtils.flatTreeSetup(document.body); + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.equal(axe.commons.text.labelVirtual(target), 'monkeys'); }); }); - describe('explicit label', function () { - it('should detect it', function () { - fixture.innerHTML = - '' + ''; + describe('explicit label', () => { + it('should detect it', () => { + fixture.innerHTML = html` + + + `; - var tree = axe.testUtils.flatTreeSetup(document.body); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const tree = axe.testUtils.flatTreeSetup(document.body); + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.equal(axe.commons.text.labelVirtual(target), 'monkeys'); }); - it('should ignore whitespace only or empty labels', function () { - fixture.innerHTML = - '' + ''; + it('should ignore whitespace only or empty labels', () => { + fixture.innerHTML = html` + + + `; - var tree = axe.testUtils.flatTreeSetup(document.body); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const tree = axe.testUtils.flatTreeSetup(document.body); + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.isNull(axe.commons.text.labelVirtual(target)); }); - it('should take precedence over implicit labels', function () { - fixture.innerHTML = - '' + - ''; + it('should take precedence over implicit labels', () => { + fixture.innerHTML = html` + + + `; - var tree = axe.testUtils.flatTreeSetup(document.body); - var target = axe.utils.querySelectorAll(tree, '#target')[0]; + const tree = axe.testUtils.flatTreeSetup(document.body); + const target = axe.utils.querySelectorAll(tree, '#target')[0]; assert.equal(axe.commons.text.labelVirtual(target), 'monkeys'); }); }); - describe('implicit label', function () { - it('should detect it', function () { - fixture.innerHTML = '
+
+ +
+
+ +
+
+ +
+
+ +
+
+
apples
+
+ This should be ignored
diff --git a/test/integration/full/region/region-fail.js b/test/integration/full/region/region-fail.js index a1fd2952a..2422f867f 100644 --- a/test/integration/full/region/region-fail.js +++ b/test/integration/full/region/region-fail.js @@ -1,26 +1,50 @@ -describe('region fail test', function () { - 'use strict'; - var results; - before(function (done) { - axe.testUtils.awaitNestedLoad(function () { - axe.run( - { runOnly: { type: 'rule', values: ['region'] } }, - function (err, r) { - assert.isNull(err); - results = r; - done(); - } - ); +describe('region fail test', () => { + let results; + before(done => { + axe.testUtils.awaitNestedLoad(() => { + axe.run({ runOnly: { type: 'rule', values: ['region'] } }, (err, r) => { + assert.isNull(err); + results = r; + done(); + }); }); }); - describe('violations', function () { - it('should find one', function () { - assert.lengthOf(results.violations[0].nodes, 1); + describe('violations', () => { + it('should find all violations', () => { + assert.lengthOf(results.violations[0].nodes, 6); }); - it('should find wrapper', function () { + it('should find wrapper', () => { assert.deepEqual(results.violations[0].nodes[0].target, ['#wrapper']); }); + + it('should find image without an alt tag', () => { + assert.deepEqual(results.violations[0].nodes[1].target, ['#img-no-alt']); + }); + + it('should find focusable image', () => { + assert.deepEqual(results.violations[0].nodes[2].target, [ + '#img-focusable' + ]); + }); + + it('should find image with global aria attr', () => { + assert.deepEqual(results.violations[0].nodes[3].target, [ + '#img-aria-global' + ]); + }); + + it('should find object with a label', () => { + assert.deepEqual(results.violations[0].nodes[4].target, [ + '#labeled-object' + ]); + }); + + it('should find div with an role of none', () => { + assert.deepEqual(results.violations[0].nodes[5].target, [ + '#none-role-div' + ]); + }); }); }); diff --git a/test/integration/full/region/region-pass.html b/test/integration/full/region/region-pass.html index 3c1a88111..1e739bb39 100644 --- a/test/integration/full/region/region-pass.html +++ b/test/integration/full/region/region-pass.html @@ -20,6 +20,16 @@ + + + + + + + +
+ +

This is a header.

diff --git a/test/integration/full/region/region-pass.js b/test/integration/full/region/region-pass.js index 12e7e2bba..02b4d1b7b 100644 --- a/test/integration/full/region/region-pass.js +++ b/test/integration/full/region/region-pass.js @@ -1,9 +1,8 @@ -describe('region pass test', function () { - 'use strict'; - var results; - before(function (done) { - axe.testUtils.awaitNestedLoad(function () { - axe.run({ runOnly: ['region'] }, function (err, r) { +describe('region pass test', () => { + let results; + before(done => { + axe.testUtils.awaitNestedLoad(() => { + axe.run({ runOnly: ['region'] }, (err, r) => { assert.isNull(err); results = r; done(); @@ -11,16 +10,16 @@ describe('region pass test', function () { }); }); - describe('passes', function () { - it('should pass nodes', function () { + describe('passes', () => { + it('should pass nodes', () => { // it seems CircleCI and localhost have different number of DOM nodes, // so as long as everything passes and nothing fails, the rule is working assert.isTrue(results.passes[0].nodes.length > 0); }); }); - describe('violations', function () { - it('should find none', function () { + describe('violations', () => { + it('should find none', () => { assert.lengthOf(results.violations, 0); }); }); diff --git a/test/integration/full/rerun/rerun.js b/test/integration/full/rerun/rerun.js index d40a02810..753adce8b 100644 --- a/test/integration/full/rerun/rerun.js +++ b/test/integration/full/rerun/rerun.js @@ -1,12 +1,10 @@ -describe('rerun axe in the same tick' + window.location.pathname, function () { - 'use strict'; - - before(function (done) { +describe(`rerun axe in the same tick${window.location.pathname}`, () => { + before(done => { axe.testUtils.awaitNestedLoad(done); }); - it('can run multiple times without interfering with itself', function (done) { - var options = { + it('can run multiple times without interfering with itself', done => { + const options = { runOnly: { type: 'rule', values: ['html-has-lang'] @@ -14,18 +12,23 @@ describe('rerun axe in the same tick' + window.location.pathname, function () { }; // First run - axe.run(options, function (err1, res1) { - assert.isNull(err1); - - // Second run, on the same tick - axe.run(options, function (err2, res2) { - assert.isNull(err2); + axe.run(options, (err1, res1) => { + try { + assert.isNull(err1); - delete res1.timestamp; - delete res2.timestamp; - assert.deepEqual(res1, res2); - done(); - }); + // Second run, on the same tick + axe.run(options, (err2, res2) => { + try { + assert.isNull(err2); + axe.testUtils.assertResultsDeepEqual(res1, res2); + done(); + } catch (e) { + done(e); + } + }); + } catch (e) { + done(e); + } }); }); }); diff --git a/test/integration/full/run-partial/after-method.js b/test/integration/full/run-partial/after-method.js index 88fdfdd6f..1046f1478 100644 --- a/test/integration/full/run-partial/after-method.js +++ b/test/integration/full/run-partial/after-method.js @@ -1,58 +1,54 @@ -describe('run-partial, after-method', function () { - 'use strict'; - var ruleName = 'heading-order'; - var runPartialRecursive = axe.testUtils.runPartialRecursive; - var clone = axe.utils.clone; +describe('run-partial, after-method', () => { + const ruleName = 'heading-order'; + const runPartialRecursive = axe.testUtils.runPartialRecursive; + const clone = axe.utils.clone; - beforeEach(function (done) { + beforeEach(done => { axe.testUtils.awaitNestedLoad(done); }); - it('gives the same passed results as axe.run with a complex "after" method', function (done) { - var options = { runOnly: ruleName }; - var context = { exclude: [] }; + it('gives the same passed results as axe.run with a complex "after" method', done => { + const options = { runOnly: ruleName }; + const context = { exclude: [] }; Promise.all(runPartialRecursive(clone(context), options)) - .then(function (partialResults) { + .then(partialResults => { return Promise.all([ axe.finishRun(partialResults, options), axe.run(clone(context), options) ]); }) - .then(function (results) { - var axeRunPartialResult = results[0]; - var axeRunResult = results[1]; + .then(results => { + const axeRunPartialResult = results[0]; + const axeRunResult = results[1]; assert.lengthOf(axeRunPartialResult.violations, 0); - axeRunPartialResult.testEnvironment = axeRunResult.testEnvironment; - axeRunPartialResult.timestamp = axeRunResult.timestamp; - assert.deepEqual(axeRunPartialResult, axeRunResult); + axe.testUtils.assertResultsDeepEqual(axeRunPartialResult, axeRunResult); done(); }) .catch(done); }); - it('gives the same failed results as axe.run with a complex "after" method', function (done) { - var options = { runOnly: ruleName }; - var context = { exclude: [['#frame1', '#frame1a']] }; + it('gives the same failed results as axe.run with a complex "after" method', done => { + const options = { runOnly: ruleName }; + const context = { exclude: [['#frame1', '#frame1a']] }; Promise.all(runPartialRecursive(clone(context), options)) - .then(function (partialResults) { + .then(partialResults => { return Promise.all([ axe.finishRun(partialResults, options), axe.run(clone(context), options) ]); }) - .then(function (results) { - var axeRunPartialResult = results[0]; - var axeRunResult = results[1]; + .then(results => { + const axeRunPartialResult = results[0]; + const axeRunResult = results[1]; assert.isObject(axeRunPartialResult); assert.isObject(axeRunResult); // Check the node is the one we expect - var nodes = axeRunPartialResult.violations[0].nodes; + const nodes = axeRunPartialResult.violations[0].nodes; assert.lengthOf(nodes, 1); assert.deepEqual(nodes[0].target, ['#frame1', 'h3']); - axeRunPartialResult.timestamp = axeRunResult.timestamp; - assert.deepEqual(axeRunPartialResult, axeRunResult); + axe.testUtils.assertResultsDeepEqual(axeRunPartialResult, axeRunResult); done(); }) .catch(done); diff --git a/test/integration/full/run-partial/context-size-focusable.js b/test/integration/full/run-partial/context-size-focusable.js index 03c56ca09..920361ae8 100644 --- a/test/integration/full/run-partial/context-size-focusable.js +++ b/test/integration/full/run-partial/context-size-focusable.js @@ -1,61 +1,57 @@ -describe('run-partial, context-size-focusable', function () { - 'use strict'; - var ruleName = 'frame-focusable-content'; - var runPartialRecursive = axe.testUtils.runPartialRecursive; - var clone = axe.utils.clone; +describe('run-partial, context-size-focusable', () => { + const ruleName = 'frame-focusable-content'; + const runPartialRecursive = axe.testUtils.runPartialRecursive; + const clone = axe.utils.clone; - beforeEach(function (done) { + beforeEach(done => { axe.testUtils.awaitNestedLoad(done); }); - it('gives the same passed results as axe.run when context.size and context.focusable are used', function (done) { - var options = { runOnly: ruleName }; - var context = { + it('gives the same passed results as axe.run when context.size and context.focusable are used', done => { + const options = { runOnly: ruleName }; + const context = { exclude: [['#fail1'], ['#fail2', 'iframe']] }; Promise.all(runPartialRecursive(clone(context), options)) - .then(function (partialResults) { + .then(partialResults => { return Promise.all([ axe.finishRun(partialResults, options), axe.run(clone(context), options) ]); }) - .then(function (results) { - var axeRunPartialResult = results[0]; - var axeRunResult = results[1]; + .then(results => { + const axeRunPartialResult = results[0]; + const axeRunResult = results[1]; assert.lengthOf(axeRunPartialResult.violations, 0); - axeRunPartialResult.testEnvironment = axeRunResult.testEnvironment; - axeRunPartialResult.timestamp = axeRunResult.timestamp; - assert.deepEqual(axeRunPartialResult, axeRunResult); + axe.testUtils.assertResultsDeepEqual(axeRunPartialResult, axeRunResult); done(); }) .catch(done); }); - it('gives the same failed results as axe.run when context.size and context.focusable are used', function (done) { - var options = { runOnly: ruleName }; + it('gives the same failed results as axe.run when context.size and context.focusable are used', done => { + const options = { runOnly: ruleName }; Promise.all(runPartialRecursive({ exclude: [] }, options)) - .then(function (partialResults) { + .then(partialResults => { return Promise.all([ axe.finishRun(partialResults, options), axe.run({ exclude: [] }, options) ]); }) - .then(function (results) { - var axeRunPartialResult = results[0]; - var axeRunResult = results[1]; + .then(results => { + const axeRunPartialResult = results[0]; + const axeRunResult = results[1]; assert.isObject(axeRunPartialResult); assert.isObject(axeRunResult); // Check the node is the one we expect - var nodes = axeRunPartialResult.violations[0].nodes; + const nodes = axeRunPartialResult.violations[0].nodes; assert.lengthOf(nodes, 2); assert.deepEqual(nodes[0].target, ['#fail1', 'html']); assert.deepEqual(nodes[1].target, ['#fail2', 'iframe', 'html']); - axeRunPartialResult.timestamp = axeRunResult.timestamp; - assert.deepEqual(axeRunPartialResult, axeRunResult); + axe.testUtils.assertResultsDeepEqual(axeRunPartialResult, axeRunResult); done(); }) .catch(done); diff --git a/test/integration/full/run-partial/initiator.js b/test/integration/full/run-partial/initiator.js index 8c0dff6a9..757ce53fe 100644 --- a/test/integration/full/run-partial/initiator.js +++ b/test/integration/full/run-partial/initiator.js @@ -1,26 +1,25 @@ -describe('run-partial, initiator', function () { - 'use strict'; - var ruleName = 'document-title'; - var runPartialRecursive = axe.testUtils.runPartialRecursive; - var clone = axe.utils.clone; +describe('run-partial, initiator', () => { + const ruleName = 'document-title'; + const runPartialRecursive = axe.testUtils.runPartialRecursive; + const clone = axe.utils.clone; - beforeEach(function (done) { + beforeEach(done => { axe.testUtils.awaitNestedLoad(done); }); - it('gives the same results as axe.run when context.initiator is used', function (done) { - var options = { runOnly: ruleName }; - var context = { exclude: [] }; + it('gives the same results as axe.run when context.initiator is used', done => { + const options = { runOnly: ruleName }; + const context = { exclude: [] }; Promise.all(runPartialRecursive(clone(context), options)) - .then(function (partialResults) { + .then(partialResults => { return Promise.all([ axe.finishRun(partialResults, options), axe.run(clone(context), options) ]); }) - .then(function (results) { - var axeRunPartialResult = results[0]; - var axeRunResult = results[1]; + .then(results => { + const axeRunPartialResult = results[0]; + const axeRunResult = results[1]; assert.lengthOf(axeRunPartialResult.violations, 0); axeRunPartialResult.testEnvironment = axeRunResult.testEnvironment; axeRunPartialResult.timestamp = axeRunResult.timestamp; diff --git a/test/integration/full/run-partial/page-level.js b/test/integration/full/run-partial/page-level.js index 589058efc..f6a3d8a4b 100644 --- a/test/integration/full/run-partial/page-level.js +++ b/test/integration/full/run-partial/page-level.js @@ -1,13 +1,12 @@ -describe('run-partial, page-level', function () { - 'use strict'; - var ruleName = 'bypass'; - var runPartialRecursive = axe.testUtils.runPartialRecursive; - var clone = axe.utils.clone; +describe('run-partial, page-level', () => { + const ruleName = 'bypass'; + const runPartialRecursive = axe.testUtils.runPartialRecursive; + const clone = axe.utils.clone; - beforeEach(function (done) { - axe.testUtils.awaitNestedLoad(function () { + beforeEach(done => { + axe.testUtils.awaitNestedLoad(() => { // Stop messing with my tests Mocha! - var heading = document.querySelector('#mocha h1'); + const heading = document.querySelector('#mocha h1'); if (heading) { heading.outerHTML = '
bypass iframe test fail
'; } @@ -15,53 +14,50 @@ describe('run-partial, page-level', function () { }); }); - it('gives the same empty results as axe.run with a pageLevel rule', function (done) { - var options = { runOnly: ruleName }; + it('gives the same empty results as axe.run with a pageLevel rule', done => { + const options = { runOnly: ruleName }; // pageLevel rules are inapplicable when they don't test the entire page - var context = { include: ['article'] }; + const context = { include: ['article'] }; Promise.all(runPartialRecursive(clone(context), options)) - .then(function (partialResults) { + .then(partialResults => { return Promise.all([ axe.finishRun(partialResults, options), axe.run(clone(context), options) ]); }) - .then(function (results) { - var axeRunPartialResult = results[0]; - var axeRunResult = results[1]; + .then(results => { + const axeRunPartialResult = results[0]; + const axeRunResult = results[1]; assert.lengthOf(axeRunPartialResult.incomplete, 0); assert.lengthOf(axeRunPartialResult.passes, 0); - axeRunPartialResult.timestamp = axeRunResult.timestamp; - axeRunPartialResult.testEnvironment = axeRunResult.testEnvironment; - assert.deepEqual(axeRunPartialResult, axeRunResult); + axe.testUtils.assertResultsDeepEqual(axeRunPartialResult, axeRunResult); done(); }) .catch(done); }); - it('gives the same failed results as axe.run with a pageLevel rule', function (done) { - var options = { runOnly: ruleName }; - var context = { exclude: [] }; + it('gives the same failed results as axe.run with a pageLevel rule', done => { + const options = { runOnly: ruleName }; + const context = { exclude: [] }; Promise.all(runPartialRecursive(clone(context), options)) - .then(function (partialResults) { + .then(partialResults => { return Promise.all([ axe.finishRun(partialResults, options), axe.run(clone(context), options) ]); }) - .then(function (results) { - var axeRunPartialResult = results[0]; - var axeRunResult = results[1]; + .then(results => { + const axeRunPartialResult = results[0]; + const axeRunResult = results[1]; assert.isObject(axeRunPartialResult); assert.isObject(axeRunResult); // Check the node is the one we expect - var nodes = axeRunPartialResult.incomplete[0].nodes; + const nodes = axeRunPartialResult.incomplete[0].nodes; assert.lengthOf(nodes, 1); assert.deepEqual(nodes[0].target, ['html']); - axeRunPartialResult.timestamp = axeRunResult.timestamp; - assert.deepEqual(axeRunPartialResult, axeRunResult); + axe.testUtils.assertResultsDeepEqual(axeRunPartialResult, axeRunResult); done(); }) .catch(done); diff --git a/test/integration/full/skip-link/skip-link-fail.js b/test/integration/full/skip-link/skip-link-fail.js index e48eec6a5..bc34d7c71 100644 --- a/test/integration/full/skip-link/skip-link-fail.js +++ b/test/integration/full/skip-link/skip-link-fail.js @@ -1,12 +1,11 @@ -describe('skip-link test pass', function () { - 'use strict'; - var results; +describe('skip-link test pass', () => { + let results; - before(function (done) { - axe.testUtils.awaitNestedLoad(function () { + before(done => { + axe.testUtils.awaitNestedLoad(() => { axe.run( { runOnly: { type: 'rule', values: ['skip-link'] } }, - function (err, r) { + (err, r) => { assert.isNull(err); results = r; done(); @@ -15,27 +14,27 @@ describe('skip-link test pass', function () { }); }); - describe('violations', function () { - it('should find 1', function () { + describe('violations', () => { + it('should find 1', () => { assert.lengthOf(results.violations, 1); }); - it('should find 1 nodes', function () { + it('should find 1 nodes', () => { assert.lengthOf(results.violations[0].nodes, 1); }); }); - describe('passes', function () { - it('should find 0', function () { + describe('passes', () => { + it('should find 0', () => { assert.lengthOf(results.passes, 0); }); }); - it('should find 0 inapplicable', function () { + it('should find 0 inapplicable', () => { assert.lengthOf(results.inapplicable, 0); }); - it('should find 0 incomplete', function () { + it('should find 0 incomplete', () => { assert.lengthOf(results.incomplete, 0); }); }); diff --git a/test/integration/full/skip-link/skip-link-pass.js b/test/integration/full/skip-link/skip-link-pass.js index 2fe6c5bff..3fa05effd 100644 --- a/test/integration/full/skip-link/skip-link-pass.js +++ b/test/integration/full/skip-link/skip-link-pass.js @@ -1,12 +1,11 @@ -describe('skip-link test pass', function () { - 'use strict'; - var results; +describe('skip-link test pass', () => { + let results; - before(function (done) { - axe.testUtils.awaitNestedLoad(function () { + before(done => { + axe.testUtils.awaitNestedLoad(() => { axe.run( { runOnly: { type: 'rule', values: ['skip-link'] } }, - function (err, r) { + (err, r) => { assert.isNull(err); results = r; done(); @@ -15,23 +14,23 @@ describe('skip-link test pass', function () { }); }); - describe('violations', function () { - it('should find 0', function () { + describe('violations', () => { + it('should find 0', () => { assert.lengthOf(results.violations, 0); }); }); - describe('passes', function () { - it('should find 3', function () { + describe('passes', () => { + it('should find 3', () => { assert.lengthOf(results.passes[0].nodes, 3); }); }); - it('should find 0 inapplicable', function () { + it('should find 0 inapplicable', () => { assert.lengthOf(results.inapplicable, 0); }); - it('should find 1 incomplete', function () { + it('should find 1 incomplete', () => { assert.lengthOf(results.incomplete, 1); }); }); diff --git a/test/integration/full/strict-csp/strict-csp.js b/test/integration/full/strict-csp/strict-csp.js index c9f0e0de7..dfbc1440d 100644 --- a/test/integration/full/strict-csp/strict-csp.js +++ b/test/integration/full/strict-csp/strict-csp.js @@ -1,13 +1,11 @@ -describe('strict-csp', function () { - 'use strict'; - - it('should parse without errors', function () { - assert.isDefined(window.axe), 'axe is not defined'; +describe('strict-csp', () => { + it('should parse without errors', () => { + (assert.isDefined(window.axe), 'axe is not defined'); assert.isDefined(window.axe.run, 'axe.run is not defined'); }); - it('should run without errors', function (done) { - axe.run(function (err, results) { + it('should run without errors', done => { + axe.run((err, results) => { assert.isNull(err); assert.isDefined(results); done(); diff --git a/test/integration/full/target-size/fixed-scroll.html b/test/integration/full/target-size/fixed-scroll.html new file mode 100644 index 000000000..0be7c89b2 --- /dev/null +++ b/test/integration/full/target-size/fixed-scroll.html @@ -0,0 +1,40 @@ + + + + Target-size position: fixed and scrolled + + + + + + + +
+ +
+

+ +

+

+ +

+
+ + + + + diff --git a/test/integration/full/target-size/fixed-scroll.js b/test/integration/full/target-size/fixed-scroll.js new file mode 100644 index 000000000..54b4886a7 --- /dev/null +++ b/test/integration/full/target-size/fixed-scroll.js @@ -0,0 +1,44 @@ +describe('target-size position: fixed and scrolled', () => { + let results; + + before(done => { + axe.testUtils.awaitNestedLoad(() => { + window.scrollTo(0, document.body.scrollHeight); + const options = { + runOnly: ['target-size'], + elementRef: true + }; + const context = { + // ignore the mocha links + exclude: '#mocha' + }; + axe.run(context, options, (err, r) => { + if (err) { + return done(err); + } + results = r; + done(); + }); + }); + }); + + describe('violations', () => { + it('should find 0', () => { + assert.lengthOf(results.violations, 0); + }); + }); + + describe('passes', () => { + it('should find 2', () => { + assert.lengthOf(results.passes[0].nodes, 2); + }); + }); + + it('should find 0 inapplicable', () => { + assert.lengthOf(results.inapplicable, 0); + }); + + it('should find 0 incomplete', () => { + assert.lengthOf(results.incomplete, 0); + }); +}); diff --git a/test/integration/full/target-size/shadow-dom.html b/test/integration/full/target-size/shadow-dom.html new file mode 100644 index 000000000..cc5e33f73 --- /dev/null +++ b/test/integration/full/target-size/shadow-dom.html @@ -0,0 +1,66 @@ + + + + Target-size shadow DOM test + + + + + + + +
+ + + + + + + + diff --git a/test/integration/full/target-size/shadow-dom.js b/test/integration/full/target-size/shadow-dom.js new file mode 100644 index 000000000..b00f165e1 --- /dev/null +++ b/test/integration/full/target-size/shadow-dom.js @@ -0,0 +1,38 @@ +describe('target-size shadow dom test', () => { + let results; + + before(done => { + axe.testUtils.awaitNestedLoad(async () => { + const options = { + runOnly: ['target-size'], + elementRef: true + }; + const context = { + // ignore the mocha links + exclude: '#mocha' + }; + results = await axe.run(context, options); + done(); + }); + }); + + describe('violations', () => { + it('should find 0', () => { + assert.lengthOf(results.violations, 0); + }); + }); + + describe('passes', () => { + it('should find 2', () => { + assert.lengthOf(results.passes[0].nodes, 2); + }); + }); + + it('should find 0 inapplicable', () => { + assert.lengthOf(results.inapplicable, 0); + }); + + it('should find 0 incomplete', () => { + assert.lengthOf(results.incomplete, 0); + }); +}); diff --git a/test/integration/full/target-size/target-size.js b/test/integration/full/target-size/target-size.js index 68287e6a8..70924bdd4 100644 --- a/test/integration/full/target-size/target-size.js +++ b/test/integration/full/target-size/target-size.js @@ -1,20 +1,19 @@ -describe('target-size test', function () { - 'use strict'; - var results; +describe('target-size test', () => { + let results; - before(function (done) { - axe.testUtils.awaitNestedLoad(function () { + before(done => { + axe.testUtils.awaitNestedLoad(() => { // Add necessary markup for axe to recognize these as components: - document.querySelectorAll('section span').forEach(function (link) { + document.querySelectorAll('section span').forEach(link => { link.setAttribute('role', 'link'); link.setAttribute('tabindex', '0'); }); - var options = { + const options = { runOnly: ['target-size'], elementRef: true }; - axe.run('section', options, function (err, r) { + axe.run('section', options, (err, r) => { if (err) { done(err); } @@ -22,36 +21,37 @@ describe('target-size test', function () { // Add some highlighting for visually identifying issues. // There are too many test cases to just do this by selector. results.violations[0] && - results.violations[0].nodes.forEach(function (node) { + results.violations[0].nodes.forEach(node => { node.element.className += ' violations'; }); results.passes[0] && - results.passes[0].nodes.forEach(function (node) { + results.passes[0].nodes.forEach(node => { node.element.className += ' passes'; }); - console.log(results); done(); }); }); }); - it('finds all passing nodes', function () { - var passResults = results.passes[0] ? results.passes[0].nodes : []; - var passedElms = document.querySelectorAll( + it('finds all passing nodes', () => { + const passResults = results.passes[0] ? results.passes[0].nodes : []; + const passedElms = document.querySelectorAll( 'section:not([hidden]) div:not([hidden]) .passed' ); - passResults.forEach(function (result) { + passResults.forEach(result => { assert.include(passedElms, result.element); }); assert.lengthOf(passResults, passedElms.length); }); - it('finds all failed nodes', function () { - var failResults = results.violations[0] ? results.violations[0].nodes : []; - var failedElms = document.querySelectorAll( + it('finds all failed nodes', () => { + const failResults = results.violations[0] + ? results.violations[0].nodes + : []; + const failedElms = document.querySelectorAll( 'section:not([hidden]) div:not([hidden]) .failed' ); - failResults.forEach(function (result) { + failResults.forEach(result => { assert.include(failedElms, result.element); }); assert.lengthOf(failResults, failedElms.length); diff --git a/test/integration/full/target-size/too-many-rects.html b/test/integration/full/target-size/too-many-rects.html index 0a90cd478..476f0e4e8 100644 --- a/test/integration/full/target-size/too-many-rects.html +++ b/test/integration/full/target-size/too-many-rects.html @@ -24,7 +24,7 @@
diff --git a/test/integration/full/target-size/too-many-rects.js b/test/integration/full/target-size/too-many-rects.js index d1364b0d3..19fcd7833 100644 --- a/test/integration/full/target-size/too-many-rects.js +++ b/test/integration/full/target-size/too-many-rects.js @@ -1,5 +1,4 @@ describe('target-size too many rects test', () => { - 'use strict'; let results; before(done => { @@ -9,6 +8,7 @@ describe('target-size too many rects test', () => { elementRef: true }; const context = { + include: '#incomplete', // ignore the incomplete table results exclude: 'table tr' }; @@ -17,7 +17,6 @@ describe('target-size too many rects test', () => { done(err); } results = r; - console.log(results); done(); }); }); diff --git a/test/integration/full/test-webdriver.js b/test/integration/full/test-webdriver.js index 89d9e8a86..956402c63 100644 --- a/test/integration/full/test-webdriver.js +++ b/test/integration/full/test-webdriver.js @@ -1,7 +1,8 @@ const { globSync } = require('glob'); const chrome = require('selenium-webdriver/chrome'); const firefox = require('selenium-webdriver/firefox'); -const chromedriver = require('chromedriver'); +const chromedriver = + process.env.CHROMEDRIVER_BIN ?? require('chromedriver').path; const args = process.argv.slice(2); @@ -26,7 +27,7 @@ function collectTestResults(driver) { const callback = arguments[arguments.length - 1]; setTimeout(() => { if (!window.mocha) { - callback('mocha-missing;' + window.location.href); + callback(`mocha-missing;${window.location.href}`); } // return the mocha results (or undefined if not finished) callback(window.mochaResults); @@ -36,9 +37,8 @@ function collectTestResults(driver) { // If there are no results, listen a little longer if (typeof result === 'string' && result.includes('mocha-missing')) { throw new Error( - 'Mocha does not exist in: ' + - result.split(';')[1] + - '\nIf using a frame, put the file in a subdirectory' + `Mocha does not exist in: ${result.split(';')[1]} +If using a frame, put the file in a subdirectory` ); } if (!result) { @@ -75,7 +75,7 @@ function runTestUrls(driver, isMobile, urls, errors) { const browserName = capabilities.get('browserName') + (capabilities.get('mobileEmulationEnabled') ? '-mobile' : ''); - console.log(url + ' [' + browserName + ']'); + console.log(`${url} [${browserName}]`); // Remember the errors (result.reports || []).forEach(err => { @@ -86,17 +86,9 @@ function runTestUrls(driver, isMobile, urls, errors) { }); // Log the result of the page tests - console[result.failures ? 'error' : 'log']( - 'passes: ' + - result.passes + - ', ' + - 'failures: ' + - result.failures + - ', ' + - 'duration: ' + - result.duration / 1000 + - 's' - ); + console[result.failures ? 'error' : 'log'](`passes: ${result.passes}, + failures: ${result.failures}, + duration: ${result.duration / 1000}s`); console.log(); }) .then(() => { @@ -118,22 +110,32 @@ function buildWebDriver(browser) { let webdriver; const mobileBrowser = browser.split('-mobile'); - // fix chrome DevToolsActivePort file doesn't exist in CricleCI (as well as a + // fix chrome DevToolsActivePort file doesn't exist in CircleCI (as well as a // host of other problems with starting Chrome). the only thing that seems to // allow Chrome to start without problems consistently is using ChromeHeadless // @see https://stackoverflow.com/questions/50642308/webdriverexception-unknown-error-devtoolsactiveport-file-doesnt-exist-while-t if (browser === 'chrome') { - const service = new chrome.ServiceBuilder(chromedriver.path).build(); + const service = new chrome.ServiceBuilder(chromedriver).build(); const options = new chrome.Options().addArguments([ - 'headless', + '--headless', '--no-sandbox', '--disable-extensions', '--disable-dev-shm-usage' ]); + + if (process.env.CHROME_BIN) { + options.setBinaryPath(process.env.CHROME_BIN); + } + webdriver = chrome.Driver.createSession(options, service); } else if (browser === 'firefox') { const options = new firefox.Options().addArguments('--headless'); + + if (process.env.FIREFOX_BIN) { + options.setBinary(process.env.FIREFOX_BIN); + } + webdriver = firefox.Driver.createSession(options); } @@ -155,7 +157,7 @@ function start(options) { const testUrls = globSync(['test/integration/full/**/*.{html,xhtml}'], { ignore: '**/frames/**/*.{html,xhtml}' }).map(url => { - return 'http://localhost:9876/' + url; + return `http://localhost:9876/${url}`; }); if ( @@ -167,7 +169,7 @@ function start(options) { ) { console.log(); console.log( - 'Skipped ' + options.browser + ' as it is not supported on this platform' + `Skipped ${options.browser} as it is not supported on this platform` ); return process.exit(); } @@ -188,10 +190,10 @@ function start(options) { // log each error and abort testErrors.forEach(err => { console.log(); - console.log('URL: ' + err.url); - console.log('Browser: ' + err.browser); - console.log('Describe: ' + err.titles.join(' > ')); - console.log('it ' + err.name); + console.log(`URL: ${err.url}`); + console.log(`Browser: ${err.browser}`); + console.log(`Describe: ${err.titles.join(' > ')}`); + console.log(`it ${err.name}`); console.log(err.stack); console.log(); }); diff --git a/test/integration/full/umd/mock-define.js b/test/integration/full/umd/mock-define.js index cd068641c..efdeaf55b 100644 --- a/test/integration/full/umd/mock-define.js +++ b/test/integration/full/umd/mock-define.js @@ -3,7 +3,6 @@ // Mock define method to collect calls to define var defineCalls = []; function define() { - 'use strict'; defineCalls.push(arguments); } define.amd = true; diff --git a/test/integration/full/umd/umd-define.js b/test/integration/full/umd/umd-define.js index d8b4c937b..47ce0e00f 100644 --- a/test/integration/full/umd/umd-define.js +++ b/test/integration/full/umd/umd-define.js @@ -1,19 +1,17 @@ /* global defineCalls */ -describe('UMD define', function () { - 'use strict'; - - it('should have atleast one umd global', function () { +describe('UMD define', () => { + it('should have atleast one umd global', () => { assert.isAtLeast(defineCalls.length, 1); }); - it('calls define and passes it axe', function () { - var call = defineCalls[defineCalls.length - 1]; + it('calls define and passes it axe', () => { + const call = defineCalls[defineCalls.length - 1]; assert.isFunction(call[2]); assert.strictEqual(call[2](), axe); }); - it('defines module name as axe-core', function () { - var call = defineCalls[defineCalls.length - 1]; + it('defines module name as axe-core', () => { + const call = defineCalls[defineCalls.length - 1]; assert.equal(call[0], 'axe-core'); }); }); diff --git a/test/integration/full/umd/umd-module-exports.js b/test/integration/full/umd/umd-module-exports.js index 96339269b..379908909 100644 --- a/test/integration/full/umd/umd-module-exports.js +++ b/test/integration/full/umd/umd-module-exports.js @@ -1,14 +1,12 @@ /* global module */ -describe('UMD module.export', function () { - 'use strict'; - - it('registers axe to module.exports', function () { +describe('UMD module.export', () => { + it('registers axe to module.exports', () => { assert.strictEqual(module.exports, axe); }); - it('does not use `require` functions', function () { - var result; - var requireRegex = /[^.]require\(([^\)])\)/g; + it('does not use `require` functions', () => { + let result; + const requireRegex = /[^.]require\(([^\)])\)/g; // This is to avoid colliding with Cypress.js which overloads all // uses of variables named `require`. @@ -22,8 +20,8 @@ describe('UMD module.export', function () { } }); - it('should include doT', function () { - var doT = axe.imports.doT; + it('should include doT', () => { + const doT = axe.imports.doT; assert(doT, 'doT is registered on axe.imports'); assert.equal(doT.name, 'doT'); }); diff --git a/test/integration/full/umd/umd-window.js b/test/integration/full/umd/umd-window.js index 636ea1e4e..1699426a4 100644 --- a/test/integration/full/umd/umd-window.js +++ b/test/integration/full/umd/umd-window.js @@ -1,52 +1,50 @@ /*global Promise */ -describe('UMD window', function () { - 'use strict'; - - it('should expose axe as a property of window', function () { +describe('UMD window', () => { + it('should expose axe as a property of window', () => { assert.property(window, 'axe'); }); - it('should expose Promise as a property of window', function () { + it('should expose Promise as a property of window', () => { assert.property(window, 'Promise'); }); - it('should resolve Promise(s)', function (done) { - var p1 = new Promise(function (resolve) { - setTimeout(function () { + it('should resolve Promise(s)', done => { + const p1 = new Promise(resolve => { + setTimeout(() => { resolve('Hello'); }); }); - var p2 = new Promise(function (resolve) { - setTimeout(function () { + const p2 = new Promise(resolve => { + setTimeout(() => { resolve('World!'); }); }); Promise.all([p1, p2]) - .then(function (values) { + .then(values => { assert.lengthOf(values, 2); assert.equal(values.join(' '), 'Hello World!'); done(); }) - .catch(function () { + .catch(() => { done(new Error('Expected to resolve.')); }); }); - it('should reject Promise', function (done) { - new Promise(function (resolve, reject) { - setTimeout(function () { + it('should reject Promise', done => { + new Promise((resolve, reject) => { + setTimeout(() => { reject(new Error('Boom!')); }); }) - .then(function () { + .then(() => { done(new Error('Expected to reject.')); }) - .catch(function (err) { + .catch(err => { assert.isDefined(err); done(); }); }); - it('should ensure axe has prototype chained keys', function () { + it('should ensure axe has prototype chained keys', () => { assert.hasAnyKeys(axe, ['utils', 'commons', 'core']); }); }); diff --git a/test/integration/full/xml-lang-mismatch/xml-lang-mismatch.fail.js b/test/integration/full/xml-lang-mismatch/xml-lang-mismatch.fail.js index df4507105..20f8d6a0e 100644 --- a/test/integration/full/xml-lang-mismatch/xml-lang-mismatch.fail.js +++ b/test/integration/full/xml-lang-mismatch/xml-lang-mismatch.fail.js @@ -1,8 +1,6 @@ -describe('html-xml-lang-mismatch test', function () { - 'use strict'; - - var results; - before(function (done) { +describe('html-xml-lang-mismatch test', () => { + let results; + before(done => { axe.run( { runOnly: { @@ -10,7 +8,7 @@ describe('html-xml-lang-mismatch test', function () { values: ['html-xml-lang-mismatch'] } }, - function (err, r) { + (err, r) => { assert.isNull(err); results = r; done(); @@ -18,12 +16,12 @@ describe('html-xml-lang-mismatch test', function () { ); }); - describe('violations', function () { - it('should find one', function () { + describe('violations', () => { + it('should find one', () => { assert.lengthOf(results.violations[0].nodes, 1); }); - it('should find html', function () { + it('should find html', () => { assert.deepEqual(results.violations[0].nodes[0].target, ['html']); }); }); diff --git a/test/integration/full/xml-lang-mismatch/xml-lang-mismatch.inapplicable.js b/test/integration/full/xml-lang-mismatch/xml-lang-mismatch.inapplicable.js index 756bc1f34..21453cb15 100644 --- a/test/integration/full/xml-lang-mismatch/xml-lang-mismatch.inapplicable.js +++ b/test/integration/full/xml-lang-mismatch/xml-lang-mismatch.inapplicable.js @@ -1,8 +1,6 @@ -describe('html-xml-lang-mismatch test', function () { - 'use strict'; - - var results; - before(function (done) { +describe('html-xml-lang-mismatch test', () => { + let results; + before(done => { axe.run( { runOnly: { @@ -10,7 +8,7 @@ describe('html-xml-lang-mismatch test', function () { values: ['html-xml-lang-mismatch'] } }, - function (err, r) { + (err, r) => { assert.isNull(err); results = r; done(); @@ -18,8 +16,8 @@ describe('html-xml-lang-mismatch test', function () { ); }); - describe('inapplicable', function () { - it('should find one', function () { + describe('inapplicable', () => { + it('should find one', () => { assert.lengthOf(results.inapplicable, 1); }); }); diff --git a/test/integration/full/xml-lang-mismatch/xml-lang-mismatch.pass.js b/test/integration/full/xml-lang-mismatch/xml-lang-mismatch.pass.js index 23de4bba4..3cb1b4910 100644 --- a/test/integration/full/xml-lang-mismatch/xml-lang-mismatch.pass.js +++ b/test/integration/full/xml-lang-mismatch/xml-lang-mismatch.pass.js @@ -1,8 +1,6 @@ -describe('html-xml-lang-mismatch test', function () { - 'use strict'; - - var results; - before(function (done) { +describe('html-xml-lang-mismatch test', () => { + let results; + before(done => { axe.run( { runOnly: { @@ -10,7 +8,7 @@ describe('html-xml-lang-mismatch test', function () { values: ['html-xml-lang-mismatch'] } }, - function (err, r) { + (err, r) => { assert.isNull(err); results = r; done(); @@ -18,12 +16,12 @@ describe('html-xml-lang-mismatch test', function () { ); }); - describe('passes', function () { - it('should find one', function () { + describe('passes', () => { + it('should find one', () => { assert.lengthOf(results.passes[0].nodes, 1); }); - it('should find html', function () { + it('should find html', () => { assert.deepEqual(results.passes[0].nodes[0].target, ['html']); }); }); diff --git a/test/integration/no-ui-reporter.js b/test/integration/no-ui-reporter.js index b95b0c80a..f2cc5a4b6 100644 --- a/test/integration/no-ui-reporter.js +++ b/test/integration/no-ui-reporter.js @@ -1,33 +1,31 @@ /*global mocha, console */ -(function () { - 'use strict'; - +(() => { if (!mocha || !mocha.reporter || !mocha.reporter('base')) { return; } - var Base = mocha.reporter('base')._reporter; + const Base = mocha.reporter('base')._reporter; mocha.reporter(function (runner) { Base.call(this, runner); - var passes = 0; - var failures = 0; + let passes = 0; + let failures = 0; - runner.on('pass', function (test) { + runner.on('pass', test => { passes++; console.log('pass: %s', test.fullTitle()); }); - runner.on('fail', function (test, err) { + runner.on('fail', (test, err) => { failures++; console.error('fail: %s -- error: %s', test.fullTitle(), err.message); }); - runner.on('end', function () { + runner.on('end', () => { console.log('end: %d/%d', passes, passes + failures); - var mochaFixture = document.getElementById('mocha'); + const mochaFixture = document.getElementById('mocha'); if (mochaFixture) { - var html = '
'; - html += passes + '/' + (failures + passes); + var html = `
`; + html += `${passes}/${failures}${passes}`; html += ' tests passed
'; mochaFixture.innerHTML = html; } diff --git a/test/integration/rules/README.md b/test/integration/rules/README.md index d9a6e54f4..071d1ed6b 100644 --- a/test/integration/rules/README.md +++ b/test/integration/rules/README.md @@ -2,9 +2,9 @@ Rule Integration tests take an HTML snippet file and runs an axe-core rule against it. The results for the run are then compared against the companion JSON file to ensure that every node returns as the expected result (passes, violation, incomplete, inapplicable). -To run the rule integration tests, run `npm run test:unit:integration`. You can run and debug the tests in a non-headless browser by running `npm run test:debug -- testDirs=integration`. +To run the rule integration tests, run `npm run test:unit:integration`. You can run and debug the tests in a non-headless browser by running `npm run test:debug`. 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. -When the tests are run, each JSON file is converted into a test suite file using [Karmas preprocessor](https://karma-runner.github.io/latest/config/preprocessors.html) and [runner.js](./runner.js) as the test suite template. +When the tests are run, each JSON file is converted into a test suite file using [a pretest step](../../../build/generate-integration-tests.js) and [runner.js](./runner.js) as the test suite template. The JSON file for a rule integration test contains the following information: diff --git a/test/integration/rules/aria-allowed-attr/failures.html b/test/integration/rules/aria-allowed-attr/failures.html index cdb251a24..9850819d6 100644 --- a/test/integration/rules/aria-allowed-attr/failures.html +++ b/test/integration/rules/aria-allowed-attr/failures.html @@ -9,3 +9,12 @@ aria-orientation="horizontal" id="fail5" > + +
+
+
+ +
+ + + diff --git a/test/integration/rules/aria-allowed-attr/failures.json b/test/integration/rules/aria-allowed-attr/failures.json index cedecf081..9e045d8ea 100644 --- a/test/integration/rules/aria-allowed-attr/failures.json +++ b/test/integration/rules/aria-allowed-attr/failures.json @@ -1,5 +1,17 @@ { "description": "aria-allowed-attr failing tests", "rule": "aria-allowed-attr", - "violations": [["#fail1"], ["#fail2"], ["#fail3"], ["#fail4"], ["#fail5"]] + "violations": [ + ["#fail1"], + ["#fail2"], + ["#fail3"], + ["#fail4"], + ["#fail5"], + ["#fail6"], + ["#fail7"], + ["#fail8"], + ["#fail9"], + ["#fail10"], + ["#fail11"] + ] } diff --git a/test/integration/rules/aria-allowed-attr/passes.html b/test/integration/rules/aria-allowed-attr/passes.html index dfef5d3ed..09aafe24c 100644 --- a/test/integration/rules/aria-allowed-attr/passes.html +++ b/test/integration/rules/aria-allowed-attr/passes.html @@ -2118,7 +2118,16 @@
- + + 40 + @@ -2160,3 +2169,45 @@ + + + +
+ + +
+ + + + + ok + diff --git a/test/integration/rules/aria-allowed-attr/passes.json b/test/integration/rules/aria-allowed-attr/passes.json index aa13ff6f7..a991c29be 100644 --- a/test/integration/rules/aria-allowed-attr/passes.json +++ b/test/integration/rules/aria-allowed-attr/passes.json @@ -102,6 +102,11 @@ ["#pass97"], ["#pass98"], ["#pass99"], - ["#pass100"] + ["#pass100"], + ["#pass101"], + ["#pass102"], + ["#pass103"], + ["#pass104"], + ["#pass105"] ] } diff --git a/test/integration/rules/aria-allowed-role/aria-allowed-role.html b/test/integration/rules/aria-allowed-role/aria-allowed-role.html index 3a2b99efa..f8cbe2971 100644 --- a/test/integration/rules/aria-allowed-role/aria-allowed-role.html +++ b/test/integration/rules/aria-allowed-role/aria-allowed-role.html @@ -207,6 +207,12 @@

>
+
@@ -219,12 +225,7 @@

- +
' + - '
' + - '
' - ); - var actual = rule.matches(vNode.actualNode); + it('return false when has any parent element with aria-hidden', () => { + const vNode = queryFixture(html` + + `); + const actual = rule.matches(vNode.actualNode); assert.isFalse(actual); }); }); diff --git a/test/rule-matches/aria-required-children-matches.js b/test/rule-matches/aria-required-children-matches.js index 319e7845b..b59bc7d6f 100644 --- a/test/rule-matches/aria-required-children-matches.js +++ b/test/rule-matches/aria-required-children-matches.js @@ -1,25 +1,23 @@ -describe('aria-required-children-matches', function () { - 'use strict'; +describe('aria-required-children-matches', () => { + const fixture = document.getElementById('fixture'); + const queryFixture = axe.testUtils.queryFixture; + let rule; - var fixture = document.getElementById('fixture'); - var queryFixture = axe.testUtils.queryFixture; - var rule; - - beforeEach(function () { + beforeEach(() => { rule = axe.utils.getRule('aria-required-children'); }); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should return true for a role that requires children', function () { - var vNode = queryFixture('
'); + it('should return true for a role that requires children', () => { + const vNode = queryFixture('
'); assert.isTrue(rule.matches(null, vNode)); }); - it('should return false for a role that does not require children', function () { - var vNode = queryFixture(''); + it('should return false for a role that does not require children', () => { + const vNode = queryFixture(''); assert.isFalse(rule.matches(null, vNode)); }); }); diff --git a/test/rule-matches/aria-required-parent-matches.js b/test/rule-matches/aria-required-parent-matches.js index 53f380839..750348a92 100644 --- a/test/rule-matches/aria-required-parent-matches.js +++ b/test/rule-matches/aria-required-parent-matches.js @@ -1,25 +1,23 @@ -describe('aria-required-parent-matches', function () { - 'use strict'; +describe('aria-required-parent-matches', () => { + const fixture = document.getElementById('fixture'); + const queryFixture = axe.testUtils.queryFixture; + let rule; - var fixture = document.getElementById('fixture'); - var queryFixture = axe.testUtils.queryFixture; - var rule; - - beforeEach(function () { + beforeEach(() => { rule = axe.utils.getRule('aria-required-parent'); }); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should return true for a role that requires parent', function () { - var vNode = queryFixture('
'); + it('should return true for a role that requires parent', () => { + const vNode = queryFixture('
'); assert.isTrue(rule.matches(null, vNode)); }); - it('should return false for a role that does not require parent', function () { - var vNode = queryFixture(''); + it('should return false for a role that does not require parent', () => { + const vNode = queryFixture(''); assert.isFalse(rule.matches(null, vNode)); }); }); diff --git a/test/rule-matches/autocomplete-matches.js b/test/rule-matches/autocomplete-matches.js index 7c914c39b..731b8f16d 100644 --- a/test/rule-matches/autocomplete-matches.js +++ b/test/rule-matches/autocomplete-matches.js @@ -1,132 +1,164 @@ -describe('autocomplete-matches', function () { - 'use strict'; - var fixture = document.getElementById('fixture'); - var queryFixture = axe.testUtils.queryFixture; - var rule = axe.utils.getRule('autocomplete-valid'); +describe('autocomplete-matches', () => { + const html = axe.testUtils.html; + const fixture = document.getElementById('fixture'); + const queryFixture = axe.testUtils.queryFixture; + const rule = axe.utils.getRule('autocomplete-valid'); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('is a function', function () { + it('is a function', () => { assert.isFunction(rule.matches); }); - it('returns true for input elements', function () { - var vNode = queryFixture(''); + it('returns true for input elements', () => { + const vNode = queryFixture(''); assert.isTrue(rule.matches(null, vNode)); }); - it('returns true for select elements', function () { - var vNode = queryFixture(''); assert.isTrue(rule.matches(null, vNode)); }); - it('returns true for textarea elements', function () { - var vNode = queryFixture(''; - var target = fixture.querySelector('textarea'); + const target = fixture.querySelector('textarea'); axe.testUtils.flatTreeSetup(fixture); assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(target))); }); - it('should not match when there is text that is out of the container with overflow hidden', function () { - fixture.innerHTML = - '
' + - 'text
'; - var target = fixture.querySelector('#target'); + it('should not match when there is text that is out of the container with overflow hidden', () => { + fixture.innerHTML = html` +
+ text +
+ `; + const target = fixture.querySelector('#target'); axe.testUtils.flatTreeSetup(fixture); assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(target))); }); - it('should match when there is text that is in the scroll reach of container', function () { - fixture.innerHTML = - '
' + - 'text
'; - var target = fixture.querySelector('#target'); + it('should match when there is text that is in the scroll reach of container', () => { + fixture.innerHTML = html` +
+ text +
+ `; + const target = fixture.querySelector('#target'); axe.testUtils.flatTreeSetup(fixture); assert.isTrue(rule.matches(target, axe.utils.getNodeFromTree(target))); }); - it('should match when there is text that is only partially out of the container', function () { - fixture.innerHTML = - '
' + - 'My text
'; - var target = fixture.querySelector('#target'); + it('should match when there is text that is only partially out of the container', () => { + fixture.innerHTML = html` +
+ My text +
+ `; + const target = fixture.querySelector('#target'); axe.testUtils.flatTreeSetup(fixture); assert.isTrue(rule.matches(target, axe.utils.getNodeFromTree(target))); }); - it('should match ', function () { + it('should match ', () => { fixture.innerHTML = ''; - var target = fixture.querySelector('input'); + const target = fixture.querySelector('input'); axe.testUtils.flatTreeSetup(fixture); assert.isTrue(rule.matches(target, axe.utils.getNodeFromTree(target))); }); - it('should not match ', function () { + it('should not match ', () => { fixture.innerHTML = ''; - var target = fixture.querySelector('input'); + const target = fixture.querySelector('input'); axe.testUtils.flatTreeSetup(fixture); assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(target))); }); - it('should not match ', function () { + it('should not match ', () => { fixture.innerHTML = ''; - var target = fixture.querySelector('input'); + const target = fixture.querySelector('input'); axe.testUtils.flatTreeSetup(fixture); assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(target))); }); - it('should not match ', function () { + it('should not match ', () => { fixture.innerHTML = ''; - var target = fixture.querySelector('input'); + const target = fixture.querySelector('input'); axe.testUtils.flatTreeSetup(fixture); assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(target))); }); - it('should not match ', function () { + it('should not match ', () => { fixture.innerHTML = ''; - var target = fixture.querySelector('input'); + const target = fixture.querySelector('input'); axe.testUtils.flatTreeSetup(fixture); // Some browsers will fallback to type=text for unknown input types (looking at you IE) if (target.type === 'color') { @@ -164,9 +189,9 @@ describe('color-contrast-matches', function () { } }); - it('should not match ', function () { + it('should not match ', () => { fixture.innerHTML = ''; - var target = fixture.querySelector('input'); + const target = fixture.querySelector('input'); axe.testUtils.flatTreeSetup(fixture); // Some browsers will fallback to type=text for unknown input types (looking at you IE) if (target.type === 'range') { @@ -174,85 +199,94 @@ describe('color-contrast-matches', function () { } }); - it('should match with options', () => { fixture.innerHTML = ''; - var target = fixture.querySelector('select'); + const target = fixture.querySelector('select'); axe.testUtils.flatTreeSetup(fixture); assert.isTrue(rule.matches(target, axe.utils.getNodeFromTree(target))); }); - it('should not match with no options', () => { fixture.innerHTML = ''; - var target = fixture.querySelector('select'); + const target = fixture.querySelector('select'); axe.testUtils.flatTreeSetup(fixture); assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(target))); }); - it('should match '; - var target = fixture.querySelector('textarea'); + const target = fixture.querySelector('textarea'); axe.testUtils.flatTreeSetup(fixture); assert.isTrue(rule.matches(target, axe.utils.getNodeFromTree(target))); }); - it('should not match