diff --git a/.flox/.gitignore b/.flox/.gitignore new file mode 100644 index 0000000000..15d71a169f --- /dev/null +++ b/.flox/.gitignore @@ -0,0 +1,4 @@ +run/ +cache/ +lib/ +log/ diff --git a/.flox/env.json b/.flox/env.json new file mode 100644 index 0000000000..91fcc84944 --- /dev/null +++ b/.flox/env.json @@ -0,0 +1,4 @@ +{ + "name": "axe-core-daisy", + "version": 1 +} diff --git a/.flox/env/manifest.lock b/.flox/env/manifest.lock new file mode 100644 index 0000000000..56029bb157 --- /dev/null +++ b/.flox/env/manifest.lock @@ -0,0 +1,157 @@ +{ + "lockfile-version": 1, + "manifest": { + "version": 1, + "install": { + "nodejs_24": { + "pkg-path": "nodejs_24" + } + }, + "vars": { + "CLICOLOR": "1", + "FLOX_DISABLE_METRICS": "true", + "FLX_VERBOSE": "${FLOX_VERBOSE:-}" + }, + "hook": { + "on-activate": "[[ ! -z \"${FLX_VERBOSE}\" ]] && echo \"##################################################\";\n[[ ! -z \"${FLX_VERBOSE}\" ]] && echo \"########## FLOX ACTIVATE...\";\n\n# alias lss='ls -alshF --color=auto'\nlss () {\n command ls -alshF --color=auto \"$@\"\n}\n\n# PWD=$(pwd);\n# [[ ! -z \"${FLX_VERBOSE}\" ]] && echo \"-----> PWD: ${PWD}\";\n# unset PWD\n\n#FLOX_ENV_PROJECT\n#_FLOX_ENV_LOG_DIR\n#FLOX_CACHE_DIR=\"$(dirname ${FLOX_ENV_CACHE})\"\n#FLOX_CACHE_DIR=\"$(realpath ${FLOX_ENV_CACHE})\"\nFLOX_CACHE_DIR=\"${FLOX_ENV_CACHE}\"\n[[ ! -z \"${FLX_VERBOSE}\" ]] && echo \"-----> FLOX_CACHE_DIR: ${FLOX_CACHE_DIR}\";\n\nif [[ ! -z \"${FLX_VERBOSE}\" ]]; then\n\necho \"=====> NODE VERSION: $(node --version)\";\nWHICH_NODE=$(which node);\necho \".....> NODE WHICH: ${WHICH_NODE}\";\nlss \"${WHICH_NODE}\";\nunset WHICH_NODE\n\necho \"=====> NPM VERSION (init): $(npm --version)\";\nWHICH_NPM=$(which npm);\necho \".....> NPM WHICH: ${WHICH_NPM}\";\nlss \"${WHICH_NPM}\";\nunset WHICH_NPM\n\necho \"-----> NPM CONFIG PREFIX (init): $(npm config get prefix)\";\necho \"-----> NPM_CONFIG_PREFIX (init): ${NPM_CONFIG_PREFIX}\";\n\necho \"-----> NPM CONFIG CACHE (init): $(npm config get cache)\"\necho \"-----> NPM_CONFIG_CACHE (init): ${NPM_CONFIG_CACHE}\";\n\nfi\n\n# envName=\"${_FLOX_ACTIVE_ENVIRONMENTS##:*}\";\n#echo \"${envName}\";\n#export NPM_CONFIG_PREFIX=\"/tmp/${envName}-npm\";\n\nexport NPM_CONFIG_PREFIX=\"${FLOX_CACHE_DIR}/NPM_PREFIX\";\n#### rm -rf \"$NPM_CONFIG_PREFIX\";\nmkdir -p \"$NPM_CONFIG_PREFIX\";\nif [[ ! -z \"${FLX_VERBOSE}\" ]]; then\necho \"-----> NPM CONFIG PREFIX: $(npm config get prefix)\";\necho \"-----> NPM_CONFIG_PREFIX: ${NPM_CONFIG_PREFIX}\";\nlss \"$NPM_CONFIG_PREFIX\";\nlss \"$NPM_CONFIG_PREFIX/bin\";\nlss \"$NPM_CONFIG_PREFIX/lib\";\nlss \"$NPM_CONFIG_PREFIX/lib/node_modules\";\nfi\n\nexport NPM_CONFIG_CACHE=\"${FLOX_CACHE_DIR}/NPM_CACHE\";\n#### rm -rf \"$NPM_CONFIG_CACHE\";\nmkdir -p \"$NPM_CONFIG_CACHE\";\nif [[ ! -z \"${FLX_VERBOSE}\" ]]; then\necho \"-----> NPM CONFIG CACHE: $(npm config get cache)\"\necho \"-----> NPM_CONFIG_CACHE: ${NPM_CONFIG_CACHE}\";\nlss \"$NPM_CONFIG_CACHE\";\nfi\n\nunset FLOX_CACHE_DIR\n\n[[ ! -z \"${FLX_VERBOSE}\" ]] && echo \"-----> NODE_PATH (init): ${NODE_PATH}\";\nexport NODE_PATH=\"$NPM_CONFIG_PREFIX/lib/node_modules${NODE_PATH:+:$NODE_PATH}\";\n[[ ! -z \"${FLX_VERBOSE}\" ]] && echo \"-----> NODE_PATH: ${NODE_PATH}\";\n\nexport PATH=\"$NPM_CONFIG_PREFIX/bin:$PATH\";\n\nnpm install -g npm --foreground-scripts 1>/dev/null\nnpm install -g npm-check-updates --foreground-scripts 1>/dev/null\n" + }, + "profile": { + "common": "[[ ! -z \"${FLX_VERBOSE}\" ]] && echo \"##################################################\";\n[[ ! -z \"${FLX_VERBOSE}\" ]] && echo \"########## FLOX PROFILE COMMON (${SHELL})...\";\n\nalias lss='ls -alshF --color=auto'\nalias flx='flox activate'\nalias flxx='FLOX_VERBOSE=1 flox activate'\n\n# PWD=$(pwd);\n# [[ ! -z \"${FLX_VERBOSE}\" ]] && echo \"-----> PWD: ${PWD}\";\n# unset PWD\n\nif [[ ! -z \"${FLX_VERBOSE}\" ]]; then\n\necho \"=====> NODE VERSION: $(node --version)\";\nWHICH_NODE=$(which node);\necho \".....> NODE WHICH: ${WHICH_NODE}\";\nlss \"${WHICH_NODE}\";\nunset WHICH_NODE\n\necho \"=====> NPM VERSION: $(npm --version)\";\nWHICH_NPM=$(which npm);\necho \".....> NPM WHICH: ${WHICH_NPM}\";\nlss \"${WHICH_NPM}\";\nunset WHICH_NPM\n\necho \"-----> NPM CONFIG PREFIX: $(npm config get prefix)\";\necho \"-----> NPM_CONFIG_PREFIX: ${NPM_CONFIG_PREFIX}\";\n\necho \"-----> NPM CONFIG CACHE: $(npm config get cache)\"\necho \"-----> NPM_CONFIG_CACHE: ${NPM_CONFIG_CACHE}\";\n\necho \"=====> NCU VERSION: $(ncu --version)\";\nWHICH_NCU=$(which ncu);\necho \".....> NCU WHICH: ${WHICH_NCU}\";\nlss \"${WHICH_NCU}\";\nunset WHICH_NCU\n\necho \"=====> GIT VERSION: $(git --version)\";\nWHICH_GIT=$(which git);\necho \".....> GIT WHICH: ${WHICH_GIT}\";\nlss \"${WHICH_GIT}\";\nunset WHICH_GIT\n\nENV=$(env);\necho \"-----> ENV: ${ENV}\";\necho \"||||||||||||||||||||||||||\";\n\necho \"-----> DEBUG: ${DEBUG}\";\n\necho \"-----> USER: ${USER}\";\necho \"-----> USERNAME: ${USERNAME}\";\n\necho \"-----> NODE_ENV: ${NODE_ENV}\";\n\ngit branch\ngit status\n\nfi\n" + }, + "options": { + "systems": [ + "aarch64-darwin", + "aarch64-linux", + "x86_64-darwin", + "x86_64-linux" + ] + } + }, + "packages": [ + { + "attr_path": "nodejs_24", + "broken": false, + "derivation": "/nix/store/0jp24w00c62cysdikz38xrnmxwzv0q4r-nodejs-24.13.0.drv", + "description": "Event-driven I/O framework for the V8 JavaScript engine", + "install_id": "nodejs_24", + "license": "MIT", + "locked_url": "https://github.com/flox/nixpkgs?rev=d6c71932130818840fc8fe9509cf50be8c64634f", + "name": "nodejs-24.13.0", + "pname": "nodejs_24", + "rev": "d6c71932130818840fc8fe9509cf50be8c64634f", + "rev_count": 942779, + "rev_date": "2026-02-08T14:52:16Z", + "scrape_date": "2026-02-09T04:38:52.160996Z", + "stabilities": [ + "unstable" + ], + "unfree": false, + "version": "24.13.0", + "outputs_to_install": [ + "out", + "out" + ], + "outputs": { + "dev": "/nix/store/ywm49y8ncwny2nr0wvs6vmaxqjiqg523-nodejs-24.13.0-dev", + "libv8": "/nix/store/67xyvmmgkisc7mr16wbpyjq2fqyz44af-nodejs-24.13.0-libv8", + "out": "/nix/store/szvl8aalaplzshakdpyq8l5yq8q1bw66-nodejs-24.13.0" + }, + "system": "aarch64-darwin", + "group": "toplevel", + "priority": 5 + }, + { + "attr_path": "nodejs_24", + "broken": false, + "derivation": "/nix/store/l64r4vxgdpi0jxp27mvr9sj2c2mq2cys-nodejs-24.13.0.drv", + "description": "Event-driven I/O framework for the V8 JavaScript engine", + "install_id": "nodejs_24", + "license": "MIT", + "locked_url": "https://github.com/flox/nixpkgs?rev=d6c71932130818840fc8fe9509cf50be8c64634f", + "name": "nodejs-24.13.0", + "pname": "nodejs_24", + "rev": "d6c71932130818840fc8fe9509cf50be8c64634f", + "rev_count": 942779, + "rev_date": "2026-02-08T14:52:16Z", + "scrape_date": "2026-02-09T05:11:13.043207Z", + "stabilities": [ + "unstable" + ], + "unfree": false, + "version": "24.13.0", + "outputs_to_install": [ + "out" + ], + "outputs": { + "dev": "/nix/store/qk4n3r5bvqzvpaq1qbpwwf47zg51wrhq-nodejs-24.13.0-dev", + "libv8": "/nix/store/rh9dg0mdq0hnp4cbdy4zdbmy6hnhxcfh-nodejs-24.13.0-libv8", + "out": "/nix/store/rpyb0275h916mxx8a0cm7yavm4qbr4j9-nodejs-24.13.0" + }, + "system": "aarch64-linux", + "group": "toplevel", + "priority": 5 + }, + { + "attr_path": "nodejs_24", + "broken": false, + "derivation": "/nix/store/xgryfaiwfymf8ndij80ssly7q2kdq8y1-nodejs-24.13.0.drv", + "description": "Event-driven I/O framework for the V8 JavaScript engine", + "install_id": "nodejs_24", + "license": "MIT", + "locked_url": "https://github.com/flox/nixpkgs?rev=d6c71932130818840fc8fe9509cf50be8c64634f", + "name": "nodejs-24.13.0", + "pname": "nodejs_24", + "rev": "d6c71932130818840fc8fe9509cf50be8c64634f", + "rev_count": 942779, + "rev_date": "2026-02-08T14:52:16Z", + "scrape_date": "2026-02-09T05:44:37.508733Z", + "stabilities": [ + "unstable" + ], + "unfree": false, + "version": "24.13.0", + "outputs_to_install": [ + "out" + ], + "outputs": { + "dev": "/nix/store/rp55bch0hqxynmxnh7q5bs586kp0dib6-nodejs-24.13.0-dev", + "libv8": "/nix/store/dvl504xzwklpfjngdnn6kcpriklwsf6l-nodejs-24.13.0-libv8", + "out": "/nix/store/xhp1pnnq7hski7hwg36vw7kpxmg56khh-nodejs-24.13.0" + }, + "system": "x86_64-darwin", + "group": "toplevel", + "priority": 5 + }, + { + "attr_path": "nodejs_24", + "broken": false, + "derivation": "/nix/store/qk40027pvikybl0lvr1wmia9n8ljf99c-nodejs-24.13.0.drv", + "description": "Event-driven I/O framework for the V8 JavaScript engine", + "install_id": "nodejs_24", + "license": "MIT", + "locked_url": "https://github.com/flox/nixpkgs?rev=d6c71932130818840fc8fe9509cf50be8c64634f", + "name": "nodejs-24.13.0", + "pname": "nodejs_24", + "rev": "d6c71932130818840fc8fe9509cf50be8c64634f", + "rev_count": 942779, + "rev_date": "2026-02-08T14:52:16Z", + "scrape_date": "2026-02-09T06:20:55.567820Z", + "stabilities": [ + "unstable" + ], + "unfree": false, + "version": "24.13.0", + "outputs_to_install": [ + "out" + ], + "outputs": { + "dev": "/nix/store/hc7fav9y6ai53nqbbpjxprwnkakl1w2d-nodejs-24.13.0-dev", + "libv8": "/nix/store/z7m56hl4rzdrfhnnkwig0ibzs1qz1x5d-nodejs-24.13.0-libv8", + "out": "/nix/store/9cyx2v23dip6p9q98384k9v06c96qskb-nodejs-24.13.0" + }, + "system": "x86_64-linux", + "group": "toplevel", + "priority": 5 + } + ] +} diff --git a/.flox/env/manifest.toml b/.flox/env/manifest.toml new file mode 100644 index 0000000000..b172443ec0 --- /dev/null +++ b/.flox/env/manifest.toml @@ -0,0 +1,200 @@ +# flox config --set-bool set_prompt false +# ==> .zshrc +# Warp-ify Flox subshell prompt: +# function git_branch_name() +# { +# branch=$({ git symbolic-ref -q HEAD || git name-rev --name-only --no-undefined --always HEAD; } 2>/dev/null | sed 's/refs\/heads\///') +# if [[ $branch == "" ]]; +# then +# : +# else +# #echo "[$branch]" +# echo '('$branch') ' +# fi +# } +# +# function flox_prompt() +# { +# FLX_ENVS="${FLOX_PROMPT_ENVIRONMENTS:-}" +# if [[ -z "${FLX_ENVS}" ]]; +# then +# : +# else +# echo 'FLX ['$FLX_ENVS'] ' +# fi +# } +# setopt prompt_subst +# autoload -U colors && colors +# export PS1='%{$fg[red]%}$(flox_prompt)%{$reset_color%}%n@%m %{$fg[green]%}%~%{$reset_color%} %{$fg[magenta]%}$(git_branch_name)%{$reset_color%}%# ' +# PROMPT="${PROMPT}"$'\n' +# printf '\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "zsh" }}\x9c' + +version = 1 + +[install] +nodejs_24.pkg-path = "nodejs_24" + +[vars] +FLOX_DISABLE_METRICS="true" +CLICOLOR="1" + +#set -euo pipefail (set -u in particular ==> undefined vars early exit) +# FLX_VERBOSE becomes fallback when FLOX_VERBOSE is empty or unset +FLX_VERBOSE="${FLOX_VERBOSE:-}" +# FLX_VERBOSE becomes fallback only when FLOX_VERBOSE is unset, not empty +# FLX_VERBOSE="${FLOX_VERBOSE-}" + +# if [[ ! -v FLX_VERBOSE ]]; then +# # unset +# elif [[ -z "${FLX_VERBOSE}" ]]; then +# # empty +# else +# neither unset nor empty +# fi + +[hook] +on-activate = ''' +[[ ! -z "${FLX_VERBOSE}" ]] && echo "##################################################"; +[[ ! -z "${FLX_VERBOSE}" ]] && echo "########## FLOX ACTIVATE..."; + +# alias lss='ls -alshF --color=auto' +lss () { + command ls -alshF --color=auto "$@" +} + +# PWD=$(pwd); +# [[ ! -z "${FLX_VERBOSE}" ]] && echo "-----> PWD: ${PWD}"; +# unset PWD + +#FLOX_ENV_PROJECT +#_FLOX_ENV_LOG_DIR +#FLOX_CACHE_DIR="$(dirname ${FLOX_ENV_CACHE})" +#FLOX_CACHE_DIR="$(realpath ${FLOX_ENV_CACHE})" +FLOX_CACHE_DIR="${FLOX_ENV_CACHE}" +[[ ! -z "${FLX_VERBOSE}" ]] && echo "-----> FLOX_CACHE_DIR: ${FLOX_CACHE_DIR}"; + +if [[ ! -z "${FLX_VERBOSE}" ]]; then + +echo "=====> NODE VERSION: $(node --version)"; +WHICH_NODE=$(which node); +echo ".....> NODE WHICH: ${WHICH_NODE}"; +lss "${WHICH_NODE}"; +unset WHICH_NODE + +echo "=====> NPM VERSION (init): $(npm --version)"; +WHICH_NPM=$(which npm); +echo ".....> NPM WHICH: ${WHICH_NPM}"; +lss "${WHICH_NPM}"; +unset WHICH_NPM + +echo "-----> NPM CONFIG PREFIX (init): $(npm config get prefix)"; +echo "-----> NPM_CONFIG_PREFIX (init): ${NPM_CONFIG_PREFIX}"; + +echo "-----> NPM CONFIG CACHE (init): $(npm config get cache)" +echo "-----> NPM_CONFIG_CACHE (init): ${NPM_CONFIG_CACHE}"; + +fi + +# envName="${_FLOX_ACTIVE_ENVIRONMENTS##:*}"; +#echo "${envName}"; +#export NPM_CONFIG_PREFIX="/tmp/${envName}-npm"; + +export NPM_CONFIG_PREFIX="${FLOX_CACHE_DIR}/NPM_PREFIX"; +#### rm -rf "$NPM_CONFIG_PREFIX"; +mkdir -p "$NPM_CONFIG_PREFIX"; +if [[ ! -z "${FLX_VERBOSE}" ]]; then +echo "-----> NPM CONFIG PREFIX: $(npm config get prefix)"; +echo "-----> NPM_CONFIG_PREFIX: ${NPM_CONFIG_PREFIX}"; +lss "$NPM_CONFIG_PREFIX"; +lss "$NPM_CONFIG_PREFIX/bin"; +lss "$NPM_CONFIG_PREFIX/lib"; +lss "$NPM_CONFIG_PREFIX/lib/node_modules"; +fi + +export NPM_CONFIG_CACHE="${FLOX_CACHE_DIR}/NPM_CACHE"; +#### rm -rf "$NPM_CONFIG_CACHE"; +mkdir -p "$NPM_CONFIG_CACHE"; +if [[ ! -z "${FLX_VERBOSE}" ]]; then +echo "-----> NPM CONFIG CACHE: $(npm config get cache)" +echo "-----> NPM_CONFIG_CACHE: ${NPM_CONFIG_CACHE}"; +lss "$NPM_CONFIG_CACHE"; +fi + +unset FLOX_CACHE_DIR + +[[ ! -z "${FLX_VERBOSE}" ]] && echo "-----> NODE_PATH (init): ${NODE_PATH}"; +export NODE_PATH="$NPM_CONFIG_PREFIX/lib/node_modules${NODE_PATH:+:$NODE_PATH}"; +[[ ! -z "${FLX_VERBOSE}" ]] && echo "-----> NODE_PATH: ${NODE_PATH}"; + +export PATH="$NPM_CONFIG_PREFIX/bin:$PATH"; + +npm install -g npm --foreground-scripts 1>/dev/null +npm install -g npm-check-updates --foreground-scripts 1>/dev/null +''' + +[profile] +common = ''' +[[ ! -z "${FLX_VERBOSE}" ]] && echo "##################################################"; +[[ ! -z "${FLX_VERBOSE}" ]] && echo "########## FLOX PROFILE COMMON (${SHELL})..."; + +alias lss='ls -alshF --color=auto' +alias flx='flox activate' +alias flxx='FLOX_VERBOSE=1 flox activate' + +# PWD=$(pwd); +# [[ ! -z "${FLX_VERBOSE}" ]] && echo "-----> PWD: ${PWD}"; +# unset PWD + +if [[ ! -z "${FLX_VERBOSE}" ]]; then + +echo "=====> NODE VERSION: $(node --version)"; +WHICH_NODE=$(which node); +echo ".....> NODE WHICH: ${WHICH_NODE}"; +lss "${WHICH_NODE}"; +unset WHICH_NODE + +echo "=====> NPM VERSION: $(npm --version)"; +WHICH_NPM=$(which npm); +echo ".....> NPM WHICH: ${WHICH_NPM}"; +lss "${WHICH_NPM}"; +unset WHICH_NPM + +echo "-----> NPM CONFIG PREFIX: $(npm config get prefix)"; +echo "-----> NPM_CONFIG_PREFIX: ${NPM_CONFIG_PREFIX}"; + +echo "-----> NPM CONFIG CACHE: $(npm config get cache)" +echo "-----> NPM_CONFIG_CACHE: ${NPM_CONFIG_CACHE}"; + +echo "=====> NCU VERSION: $(ncu --version)"; +WHICH_NCU=$(which ncu); +echo ".....> NCU WHICH: ${WHICH_NCU}"; +lss "${WHICH_NCU}"; +unset WHICH_NCU + +echo "=====> GIT VERSION: $(git --version)"; +WHICH_GIT=$(which git); +echo ".....> GIT WHICH: ${WHICH_GIT}"; +lss "${WHICH_GIT}"; +unset WHICH_GIT + +ENV=$(env); +echo "-----> ENV: ${ENV}"; +echo "||||||||||||||||||||||||||"; + +echo "-----> DEBUG: ${DEBUG}"; + +echo "-----> USER: ${USER}"; +echo "-----> USERNAME: ${USERNAME}"; + +echo "-----> NODE_ENV: ${NODE_ENV}"; + +git branch +git status + +fi +''' + +[services] + +[options] +systems = ["aarch64-darwin", "aarch64-linux", "x86_64-darwin", "x86_64-linux"] diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 545beb920f..fa09822db9 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -10,7 +10,7 @@ jobs: # 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 + if: ${{ false }} # github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest timeout-minutes: 5 steps: diff --git a/.github/workflows/semantic-pr-title.yml b/.github/workflows/semantic-pr-title.yml index 181cd23986..8ae2854010 100644 --- a/.github/workflows/semantic-pr-title.yml +++ b/.github/workflows/semantic-pr-title.yml @@ -10,6 +10,7 @@ on: jobs: semantic-pr-title: + if: ${{ false }} runs-on: ubuntu-latest steps: - uses: dequelabs/semantic-pr-title@v1 diff --git a/.gitignore b/.gitignore index 3331c6fdbb..76363204b3 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,12 @@ typings/axe-core/axe-core-tests.js # doc doc/rule-descriptions.*.md + +.history + +.flox/run +.flox/cache +.flox/lib +.flox/log +.flox/NPM_PREFIX +.flox/NPM_CACHE diff --git a/README.md b/README.md index 9be7bd46e2..daeb029f4c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,48 @@ +DEV: `HUSKY=0 npm i` (otherwise `"prepare": "husky"` in `w3c/aria-practices` fails https://github.com/w3c/aria-practices/blob/84b921a0c6646d2ddabaa94d918e165a1093daeb/package.json#L27 ) + +- `rm -rf node_modules/ && rm -f package-lock.json && HUSKY=0 HUSKY_SKIP_HOOKS=0 HUSKY_SKIP_INSTALL=0 npm install --foreground-scripts --ignore-scripts` +- `npm run build` +- `npx husky` +- `rm -rf ~/.browser-driver-manager && npx browser-driver-manager install chromedriver --verbose` +- `cat ~/.browser-driver-manager/.env` +- /// `source ~/.browser-driver-manager/.env` +- /// `env | grep -i CHROME` +- /// `echo $CHROME_TEST_PATH` +- /// `export CHROME_BIN="${CHROME_TEST_PATH}"` +- `killall -9 "Google Chrome"` +- `rm -rf "~/Library/Application Support/Google"` +- `npm run test:chromeheadless` +- `killall -9 "Google Chrome"` +- `rm -rf "~/Library/Application Support/Google"` +- `npm run test:chrome` +- `killall -9 "Google Chrome"` +- `rm -rf "~/Library/Application Support/Google"` + +`--ignore-scripts` must be used, see: +aria-practices@0.0.0 prepare +fsevents@2.3.2 install +pre-commit@1.2.2 install +spawn-sync@1.0.15 postinstall +act-tools@1.0.0 postinstall +act-tools@1.0.0 prebuild +act-tools@1.0.0 build +act-tools@1.0.0 prepare +==> husky install!! (not skipped by `export HUSKY=0; export HUSKY_SKIP_HOOKS=0; export HUSKY_SKIP_INSTALL=0;` ???) +https://github.com/act-rules/act-tools/blob/31ea4ae3553f1d4be885edf7568e8461b04a927a/package.json#L21C17-L21C22 + +`npm cache clean --force` and/or `rm -f .git/hooks/pre-commit` might be necessary. + +// _ `cd node_modules && cd aria-practices && HUSKY=0 HUSKY_SKIP_HOOKS=0 HUSKY_SKIP_INSTALL=0 npm run prepare && cd ../..` +// _ `cd node_modules && cd aria-practices && export HUSKY=0; export HUSKY_SKIP_HOOKS=0; export HUSKY_SKIP_INSTALL=0; npm run prepare --foreground-scripts; cd ../..` + +`~/.browser-driver-manager/.env` +==> +``` +CHROME_TEST_PATH="/Users/U/.browser-driver-manager/chrome/mac_arm-145.0.7632.46/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" +CHROMEDRIVER_TEST_PATH="/Users/U/.browser-driver-manager/chromedriver/mac_arm-145.0.7632.46/chromedriver-mac-arm64/chromedriver" +CHROME_TEST_VERSION="145.0.7632.46" +``` + # axe-core [![License](https://img.shields.io/npm/l/axe-core.svg?color=c41)](LICENSE) diff --git a/bower.json b/bower.json index d1d2ba54d0..5d3f61ce1b 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "axe-core", - "version": "4.11.1", + "version": "4.11.1-canary.1", "deprecated": true, "contributors": [ { diff --git a/build/configure.js b/build/configure.js index a8a1da734f..03ee29b8ad 100644 --- a/build/configure.js +++ b/build/configure.js @@ -10,10 +10,8 @@ var { encode } = require('html-entities'); var packageJSON = require('../package.json'); var doTRegex = /\{\{.+?\}\}/g; -var axeVersion = packageJSON.version.substring( - 0, - packageJSON.version.lastIndexOf('.') -); +var _v = packageJSON.version.replace(/-\w+\.\w+$/, ''); +var axeVersion = _v.substring(0, _v.lastIndexOf('.')); var descriptionTableHeader = '| Rule ID | Description | Impact | Tags | Issue Type | [ACT Rules](https://www.w3.org/WAI/standards-guidelines/act/rules/) |\n| :------- | :------- | :------- | :------- | :------- | :------- |\n'; diff --git a/build/tasks/update-help.js b/build/tasks/update-help.js index 417a94048c..0e947549fb 100644 --- a/build/tasks/update-help.js +++ b/build/tasks/update-help.js @@ -9,7 +9,7 @@ module.exports = function (grunt) { var options = this.options({ version: '1.0.0' }); - var v = options.version.split('.'); + var v = options.version.replace(/-\w+\.\w+$/, '').split('.'); v.pop(); var baseUrl = 'https://dequeuniversity.com/rules/axe/' + v.join('.') + '/'; diff --git a/build/tasks/validate.js b/build/tasks/validate.js index ea601806ce..ab99d820c8 100644 --- a/build/tasks/validate.js +++ b/build/tasks/validate.js @@ -310,6 +310,7 @@ function validateRule({ tags, metadata }) { const miscTags = ['ACT', 'experimental', 'review-item', 'deprecated']; const categories = [ + 'epub', 'aria', 'color', 'forms', diff --git a/doc/examples/qunit/Gruntfile.js b/doc/examples/qunit/Gruntfile.js index 19c11eedf5..a2a8f6dc6d 100644 --- a/doc/examples/qunit/Gruntfile.js +++ b/doc/examples/qunit/Gruntfile.js @@ -5,7 +5,7 @@ module.exports = function (grunt) { grunt.initConfig({ qunit: { - all: ['test/**/*.html'], + all: ['test/**/*.html', 'test/**/*__.xhtml'], options: { puppeteer: { args: ['--disable-web-security', '--allow-file-access-from-files'] diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index 6858eb6300..12e787941e 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -87,9 +87,10 @@ These rules are disabled by default, until WCAG 2.2 is more widely adopted and required. -| Rule ID | Description | Impact | Tags | Issue Type | [ACT Rules](https://www.w3.org/WAI/standards-guidelines/act/rules/) | -| :------------------------------------------------------------------------------------------------ | :-------------------------------------------------- | :------ | :--------------------------------------------- | :------------------------- | :------------------------------------------------------------------ | -| [target-size](https://dequeuniversity.com/rules/axe/4.11/target-size?application=RuleDescription) | Ensure touch targets have sufficient size and space | Serious | cat.sensory-and-visual-cues, wcag22aa, wcag258 | failure, needs review | | +| Rule ID | Description | Impact | Tags | Issue Type | [ACT Rules](https://www.w3.org/WAI/standards-guidelines/act/rules/) | +| :-------------------------------------------------------------------------------------------------------- | :-------------------------------------------------- | :------- | :--------------------------------------------- | :------------------------- | :------------------------------------------------------------------ | +| [pagebreak-label](https://dequeuniversity.com/rules/axe/4.11/pagebreak-label?application=RuleDescription) | Ensure page markers have an accessible label | Moderate | cat.epub | failure, needs review | | +| [target-size](https://dequeuniversity.com/rules/axe/4.11/target-size?application=RuleDescription) | Ensure touch targets have sufficient size and space | Serious | cat.sensory-and-visual-cues, wcag22aa, wcag258 | failure, needs review | | ## Best Practices Rules @@ -104,6 +105,7 @@ Rules that do not necessarily conform to WCAG success criterion but are industry | [aria-treeitem-name](https://dequeuniversity.com/rules/axe/4.11/aria-treeitem-name?application=RuleDescription) | Ensure every ARIA treeitem node has an accessible name | Serious | cat.aria, best-practice | failure, needs review | | | [empty-heading](https://dequeuniversity.com/rules/axe/4.11/empty-heading?application=RuleDescription) | Ensure headings have discernible text | Minor | cat.name-role-value, best-practice | failure, needs review | [ffd0e9](https://act-rules.github.io/rules/ffd0e9) | | [empty-table-header](https://dequeuniversity.com/rules/axe/4.11/empty-table-header?application=RuleDescription) | Ensure table headers have discernible text | Minor | cat.name-role-value, best-practice | failure, needs review | | +| [epub-type-has-matching-role](https://dequeuniversity.com/rules/axe/4.11/epub-type-has-matching-role?application=RuleDescription) | Ensure the element has an ARIA role matching its epub:type | Moderate | cat.aria, best-practice | failure | | | [frame-tested](https://dequeuniversity.com/rules/axe/4.11/frame-tested?application=RuleDescription) | Ensure <iframe> and <frame> elements contain the axe-core script | Critical | cat.structure, best-practice, review-item | failure, needs review | | | [heading-order](https://dequeuniversity.com/rules/axe/4.11/heading-order?application=RuleDescription) | Ensure the order of headings is semantically correct | Moderate | cat.semantics, best-practice | failure, needs review | | | [image-redundant-alt](https://dequeuniversity.com/rules/axe/4.11/image-redundant-alt?application=RuleDescription) | Ensure image alternative is not repeated as text | Minor | cat.text-alternatives, best-practice | failure | | diff --git a/lib/checks/aria/aria-prohibited-attr-evaluate.js b/lib/checks/aria/aria-prohibited-attr-evaluate.js index bee8b99a7b..4d22c142a7 100644 --- a/lib/checks/aria/aria-prohibited-attr-evaluate.js +++ b/lib/checks/aria/aria-prohibited-attr-evaluate.js @@ -35,11 +35,11 @@ export default function ariaProhibitedAttrEvaluate( const elementsAllowedAriaLabel = options?.elementsAllowedAriaLabel || []; const { nodeName } = virtualNode.props; const role = getRole(virtualNode, { - chromium: true, + chromium: true, + dpub: true, // this check allows fallback roles. For example, `
` is legal. fallback: true }); - const prohibitedList = listProhibitedAttrs( virtualNode, role, diff --git a/lib/checks/aria/aria-required-children-evaluate.js b/lib/checks/aria/aria-required-children-evaluate.js index 1e7512d960..f2662ec75c 100644 --- a/lib/checks/aria/aria-required-children-evaluate.js +++ b/lib/checks/aria/aria-required-children-evaluate.js @@ -75,6 +75,8 @@ export default function ariaRequiredChildrenEvaluate( */ function getOwnedRoles(virtualNode, required) { let vNode; + const parentRole = getRole(virtualNode, { dpub: true }); + const ownedRoles = []; const ownedVirtual = getOwnedVirtual(virtualNode); while ((vNode = ownedVirtual.shift())) { @@ -85,7 +87,8 @@ function getOwnedRoles(virtualNode, required) { continue; } - const role = getRole(vNode, { noPresentational: true }); + const role = getRole(vNode, { noPresentational: true, dpub: true }); + const globalAriaAttr = getGlobalAriaAttr(vNode); const hasGlobalAriaOrFocusable = !!globalAriaAttr || isFocusable(vNode); @@ -96,7 +99,9 @@ function getOwnedRoles(virtualNode, required) { if ( (!role && !hasGlobalAriaOrFocusable) || (['group', 'rowgroup'].includes(role) && - required.some(requiredRole => requiredRole === role)) + required.some(requiredRole => requiredRole === role)) || + (['list'].includes(role) && + ['doc-bibliography', 'doc-endnotes'].includes(parentRole)) ) { ownedVirtual.push(...vNode.children); } else if (role || hasGlobalAriaOrFocusable) { diff --git a/lib/checks/aria/matching-aria-role-evaluate.js b/lib/checks/aria/matching-aria-role-evaluate.js new file mode 100644 index 0000000000..ca1d7316bc --- /dev/null +++ b/lib/checks/aria/matching-aria-role-evaluate.js @@ -0,0 +1,190 @@ +import { tokenList } from '../../core/utils'; +import standards from '../../standards'; +import { getRole } from '../../commons/aria'; +import matchesSelector from '../../core/utils/element-matches'; + +function matchingAriaRoleEvaluate(node) { + // https://idpf.github.io/epub-guides/epub-aria-authoring/#sec-mappings + // https://www.w3.org/TR/dpub-aam-1.0/#mapping_role_table + // https://w3c.github.io/publ-cg/guides/aria-mapping.html#mapping-table + const mappings = new Map([ + ['abstract', 'doc-abstract'], + ['acknowledgments', 'doc-acknowledgments'], + ['afterword', 'doc-afterword'], + // ['answer', '??'], + // ['answers', '??'], + ['appendix', 'doc-appendix'], + // ['assessment', '??'], + // ['assessments', '??'], + // ['backmatter', '??'], + // ['balloon', '??'], + // ['backlink', 'doc-backlink'], // ?? + ['biblioentry', 'doc-biblioentry'], + ['bibliography', 'doc-bibliography'], + ['biblioref', 'doc-biblioref'], + // ['bodymatter', '??'], + // ['bridgehead', '??'], + // ['case-study', '??'], + ['chapter', 'doc-chapter'], + ['colophon', 'doc-colophon'], + // ['concluding-sentence', '??'], + ['conclusion', 'doc-conclusion'], + // ['contributors', '??'], + // ['copyright-page', '??'], + // ['cover', '??'], + // ['cover-image', 'doc-cover'], // ?? + // ['covertitle', '??'], + ['credit', 'doc-credit'], + ['credits', 'doc-credits'], + ['dedication', 'doc-dedication'], + // ['division', '??'], + ['endnote', 'doc-endnote'], + ['endnotes', 'doc-endnotes'], + ['epigraph', 'doc-epigraph'], + ['epilogue', 'doc-epilogue'], + ['errata', 'doc-errata'], + // ['example', 'doc-example'], + // ['feedback', '??'], + ['figure', 'figure'], // ARIA + // ['fill-in-the-blank-problem', '??'], + ['footnote', 'doc-footnote'], + // ['footnotes', '??'], + ['foreword', 'doc-foreword'], + // ['frontmatter', '??'], + // ['fulltitle', '??'], + // ['general-problem', '??'], + ['glossary', 'doc-glossary'], + ['glossdef', 'definition'], // ARIA + ['glossref', 'doc-glossref'], + ['glossterm', 'term'], // ARIA + // ['halftitle', '??'], + // ['halftitlepage', '??'], + // ['imprimatur', '??'], + // ['imprint', '??'], + ['help', 'doc-tip'], // ?? + ['index', 'doc-index'], + // ['index-editor-note', '??'], + // ['index-entry', '??'], + // ['index-entry-list', '??'], + // ['index-group', '??'], + // ['index-headnotes', '??'], + // ['index-legend', '??'], + // ['index-locator', '??'], + // ['index-locator-list', '??'], + // ['index-locator-range', '??'], + // ['index-term', '??'], + // ['index-term-categories', '??'], + // ['index-term-category', '??'], + // ['index-xref-preferred', '??'], + // ['index-xref-related', '??'], + ['introduction', 'doc-introduction'], + // ['keyword', '??'], + // ['keywords', '??'], + // ['label', '??'], + // ['landmarks', 'directory'], // ARIA (SKIPPED! NavDoc) + // ['learning-objective', '??'], + // ['learning-objectives', '??'], + // ['learning-outcome', '??'], + // ['learning-outcomes', '??'], + // ['learning-resource', '??'], + // ['learning-resources', '??'], + // ['learning-standard', '??'], + // ['learning-standards', '??'], + ['list', 'list'], // ARIA + ['list-item', 'listitem'], // ARIA + // ['loa', '??'], + // ['loi', '??'], + // ['lot', '??'], + // ['lov', '??'], + // ['match-problem', '??'], + // ['multiple-choice-problem', '??'], + ['noteref', 'doc-noteref'], + ['notice', 'doc-notice'], + // ['ordinal', '??'], + // ['other-credits', '??'], + ['page-list', 'doc-pagelist'], + ['pagebreak', 'doc-pagebreak'], + // ['panel', '??'], + // ['panel-group', '??'], + ['part', 'doc-part'], + // ['practice', '??'], + // ['practices', '??'], + // ['preamble', '??'], + ['preface', 'doc-preface'], + ['prologue', 'doc-prologue'], + ['pullquote', 'doc-pullquote'], + ['qna', 'doc-qna'], + // ['question', '??'], + ['referrer', 'doc-backlink'], + // ['revision-history', '??'], + // ['seriespage', '??'], + // ['sound-area', '??'], + // ['subchapter', '??'], + ['subtitle', 'doc-subtitle'], + ['table', 'table'], + ['table-cell', 'cell'], + ['table-row', 'row'], + // ['text-area', '??'], + ['tip', 'doc-tip'], + // ['title', '??'], + // ['titlepage', '??'], + ['toc', 'doc-toc'] + // ['toc-brief', '??'], + // ['topic-sentence', '??'], + // ['true-false-problem', '??'], + // ['volume', '??'], + ]); + + const hasXmlEpubType = node.hasAttributeNS( + 'http://www.idpf.org/2007/ops', + 'type' + ); + if ( + hasXmlEpubType || + node.hasAttribute('epub:type') // for unit tests that are not XML-aware due to fixture.innerHTML + ) { + // abort if descendant of landmarks nav (nav with epub:type=landmarks) + if ( + (hasXmlEpubType && matchesSelector(node, 'nav[*|type~="landmarks"] *')) || + matchesSelector(node, 'nav[epub\\:type~="landmarks"] *') + ) { + // console.log('BREAKPOINT'); + // throw new Error('BREAKPOINT'); + return true; + } + + // iterate for each epub:type value + var types = tokenList( + hasXmlEpubType + ? node.getAttributeNS('http://www.idpf.org/2007/ops', 'type') + : node.getAttribute('epub:type') + ); + for (const type of types) { + // If there is a 1-1 mapping, check that the role is set (best practice) + if (mappings.has(type)) { + // Note: using axe’s `getRole` util returns the effective role of the element + // (either explicitly set with the role attribute or implicit) + // So this works for types mapping to core ARIA roles (eg. glossref/glossterm). + const mappedRole = mappings.get(type); + const role = getRole(node, { dpub: true }); + const roleDefinition = standards.ariaRoles[mappedRole]; + if (!roleDefinition || roleDefinition.deprecated) { + return true; + } + // if (mappedRole !== role) { + // console.log('BREAKPOINT: ', type, mappedRole, role); + // // throw new Error('BREAKPOINT'); + // } + return mappedRole === role; + } else { + // e.g. cover, landmarks + // console.log('BREAKPOINT: ', type); + // throw new Error('BREAKPOINT'); + } + } + } + + return true; +} + +export default matchingAriaRoleEvaluate; diff --git a/lib/checks/aria/matching-aria-role.json b/lib/checks/aria/matching-aria-role.json new file mode 100644 index 0000000000..fb78acbe75 --- /dev/null +++ b/lib/checks/aria/matching-aria-role.json @@ -0,0 +1,11 @@ +{ + "id": "matching-aria-role", + "evaluate": "matching-aria-role-evaluate", + "metadata": { + "impact": "minor", + "messages": { + "pass": "Element has an ARIA role matching its epub:type", + "fail": "Element has no ARIA role matching its epub:type" + } + } +} diff --git a/lib/checks/landmarks/landmark-is-unique-after.js b/lib/checks/landmarks/landmark-is-unique-after.js index fd4eb2ec1f..71628c861c 100644 --- a/lib/checks/landmarks/landmark-is-unique-after.js +++ b/lib/checks/landmarks/landmark-is-unique-after.js @@ -1,11 +1,20 @@ function landmarkIsUniqueAfter(results) { const uniqueLandmarks = []; + // console.log("landmarkIsUniqueAfter results: ", JSON.stringify(results, null, 4)); + // filter out landmark elements that share the same role and accessible text // so every non-unique landmark isn't reported as a failure (just the first) - return results.filter(currentResult => { - const findMatch = someResult => { + var filtered = results.filter(currentResult => { + if (!currentResult.data) { + // console.log('landmarkIsUniqueAfterlandmarkIsUniqueAfter NO DATA???!!!'); + return false; + } + + var findMatch = someResult => { return ( + // currentResult.data.isLandmark && + // someResult.data.isLandmark && currentResult.data.role === someResult.data.role && currentResult.data.accessibleText === someResult.data.accessibleText ); @@ -22,6 +31,9 @@ function landmarkIsUniqueAfter(results) { currentResult.relatedNodes = []; return true; }); + + // console.log("landmarkIsUniqueAfter filtered: ", JSON.stringify(filtered, null, 4)); + return filtered; } export default landmarkIsUniqueAfter; diff --git a/lib/checks/landmarks/landmark-is-unique-evaluate.js b/lib/checks/landmarks/landmark-is-unique-evaluate.js index f8375fed5f..0a6e9151d7 100644 --- a/lib/checks/landmarks/landmark-is-unique-evaluate.js +++ b/lib/checks/landmarks/landmark-is-unique-evaluate.js @@ -1,11 +1,40 @@ -import { getRole } from '../../commons/aria'; +import { getRole } from '../../commons/aria'; // getRoleType import { accessibleTextVirtual } from '../../commons/text'; +// import { getAriaRolesByType } from '../../commons/standards'; function landmarkIsUniqueEvaluate(node, options, virtualNode) { - const role = getRole(node); - let accessibleText = accessibleTextVirtual(virtualNode); + var role = getRole(node, { dpub: true }); // fallback: true + if (!role) { + // this.data({ role: '', accessibleText: '', isLandmark: null }); + // console.log('landmarkIsUniqueEvaluate landmarkIsUniqueEvaluate landmarkIsUniqueEvaluate NO ROLE???!!!'); + return false; + } + + // var landmarks = getAriaRolesByType('landmark'); + // var roleType = getRoleType(role); + // var isLandmark = + // roleType === 'landmark' || + // landmarks.includes(roleType) || + // landmarks.includes(role); + + // if (!isLandmark) { + // // this.data({ role: '', accessibleText: '', isLandmark: null }); + // return false; + // } + // throw new Error('BREAK'); + + var accessibleText = accessibleTextVirtual(virtualNode); + + // console.log('\n\n ))))) ', virtualNode.props ? virtualNode.props.nodeName : '!virtualNode.props', role, roleType, JSON.stringify(landmarks), isLandmark, " [[" + accessibleText + "]]") + accessibleText = accessibleText ? accessibleText.toLowerCase() : null; - this.data({ role: role, accessibleText: accessibleText }); + + this.data({ + role: role, + accessibleText: accessibleText + // isLandmark: isLandmark + }); + this.relatedNodes([node]); return true; diff --git a/lib/checks/lists/listitem-evaluate.js b/lib/checks/lists/listitem-evaluate.js index dde5dcb4f4..13bb2c6278 100644 --- a/lib/checks/lists/listitem-evaluate.js +++ b/lib/checks/lists/listitem-evaluate.js @@ -1,4 +1,8 @@ -import { isValidRole, getExplicitRole } from '../../commons/aria'; +import { + getExplicitRole, + getSuperClassRole, + isValidRole +} from '../../commons/aria'; export default function listitemEvaluate(node, options, virtualNode) { const { parent } = virtualNode; @@ -15,6 +19,11 @@ export default function listitemEvaluate(node, options, virtualNode) { } if (parentRole && isValidRole(parentRole)) { + const sup = getSuperClassRole(parentRole); + if (sup && sup.includes('list')) { + return true; + } + this.data({ messageKey: 'roleNotValid' }); diff --git a/lib/checks/lists/only-listitems-evaluate.js b/lib/checks/lists/only-listitems-evaluate.js index 93ab74adfa..ff9d27d728 100644 --- a/lib/checks/lists/only-listitems-evaluate.js +++ b/lib/checks/lists/only-listitems-evaluate.js @@ -1,5 +1,6 @@ import { isVisibleToScreenReaders } from '../../commons/dom'; -import { getRole } from '../../commons/aria'; + +import { getRole, getSuperClassRole } from '../../commons/aria'; /** * @deprecated @@ -27,7 +28,11 @@ function onlyListitemsEvaluate(node, options, virtualNode) { isEmpty = false; const isLi = actualNode.nodeName.toUpperCase() === 'LI'; const role = getRole(vNode); - const isListItemRole = role === 'listitem'; + + const sup = getSuperClassRole(role); + const isListItemRole = + role === 'listitem' || (sup && sup.includes('listitem')); + // const isListItemRole = role === 'listitem'; if (!isLi && !isListItemRole) { badNodes.push(actualNode); diff --git a/lib/commons/aria/get-element-unallowed-roles.js b/lib/commons/aria/get-element-unallowed-roles.js index 92544d47dd..037a8b0092 100644 --- a/lib/commons/aria/get-element-unallowed-roles.js +++ b/lib/commons/aria/get-element-unallowed-roles.js @@ -8,10 +8,10 @@ import { tokenList, isHtmlElement, nodeLookup } from '../../core/utils'; // HTML elements (img, link, etc.) const dpubRoles = [ 'doc-backlink', - 'doc-biblioentry', + // 'doc-biblioentry', 'doc-biblioref', 'doc-cover', - 'doc-endnote', + // 'doc-endnote', 'doc-glossref', 'doc-noteref' ]; diff --git a/lib/commons/aria/get-super-class-role.js b/lib/commons/aria/get-super-class-role.js new file mode 100644 index 0000000000..52544dd100 --- /dev/null +++ b/lib/commons/aria/get-super-class-role.js @@ -0,0 +1,21 @@ +import standards from '../../standards'; + +/** + * Get the "superclassRole" of role + * @method getSuperClassRole + * @memberof axe.commons.aria + * @instance + * @param {String} role The role to check + * @return {Mixed} String if a matching role and its superclassRole are found, otherwise `null` + */ +function getSuperClassRole(role) { + const roleDef = standards.ariaRoles[role]; + + if (!roleDef) { + return null; + } + + return roleDef.superclassRole; +} + +export default getSuperClassRole; diff --git a/lib/commons/aria/index.js b/lib/commons/aria/index.js index 9eb5bb3a88..c41443ebfd 100644 --- a/lib/commons/aria/index.js +++ b/lib/commons/aria/index.js @@ -12,6 +12,7 @@ export { default as getExplicitRole } from './get-explicit-role'; export { default as getImplicitRole } from './implicit-role'; export { default as getOwnedVirtual } from './get-owned-virtual'; export { default as getRoleType } from './get-role-type'; +export { default as getSuperClassRole } from './get-super-class-role'; export { default as getRole } from './get-role'; export { default as getRolesByType } from './get-roles-by-type'; export { default as getRolesWithNameFromContents } from './get-roles-with-name-from-contents'; diff --git a/lib/commons/aria/lookup-table.js b/lib/commons/aria/lookup-table.js index 84700f8f1c..11d3aa10d6 100644 --- a/lib/commons/aria/lookup-table.js +++ b/lib/commons/aria/lookup-table.js @@ -489,7 +489,7 @@ lookupTable.role = { owned: null, nameFrom: ['author'], context: null, - implicit: ['dd', 'dfn'], + implicit: ['dd'], // DAISY-AXE: remove 'dfn' which has implicit 'term' role, see https://www.w3.org/TR/html-aria/#docconformance unsupported: false }, dialog: { @@ -590,7 +590,7 @@ lookupTable.role = { ] }, 'doc-biblioentry': { - type: 'listitem', + type: 'structure', attributes: { allowed: [ 'aria-expanded', @@ -611,9 +611,11 @@ lookupTable.role = { attributes: { allowed: ['aria-expanded', 'aria-errormessage'] }, - owned: { - one: ['doc-biblioentry'] - }, + owned: null, + // owned: { + // // one: ['doc-biblioentry'] + // one: ['list'] + // }, nameFrom: ['author'], context: null, unsupported: false, @@ -714,7 +716,7 @@ lookupTable.role = { allowedElements: ['section'] }, 'doc-endnote': { - type: 'listitem', + type: 'structure', attributes: { allowed: [ 'aria-expanded', @@ -735,9 +737,11 @@ lookupTable.role = { attributes: { allowed: ['aria-expanded', 'aria-errormessage'] }, - owned: { - one: ['doc-endnote'] - }, + owned: null, + // owned: { + // // one: ['doc-endnote'] + // one: ['list'] + // }, namefrom: ['author'], context: null, unsupported: false, @@ -775,8 +779,11 @@ lookupTable.role = { unsupported: false, allowedElements: ['section'] }, + // https://www.w3.org/TR/dpub-aria-1.0/#doc-example + // ==> (was 'section' now 'figure') + // https://www.w3.org/TR/dpub-aria-1.1/#doc-example 'doc-example': { - type: 'section', + type: 'structure', attributes: { allowed: ['aria-expanded', 'aria-errormessage'] }, @@ -813,7 +820,8 @@ lookupTable.role = { attributes: { allowed: ['aria-expanded', 'aria-errormessage'] }, - owned: ['term', 'definition'], + owned: null, + // owned: ['term', 'definition'], namefrom: ['author'], context: null, unsupported: false, @@ -895,6 +903,7 @@ lookupTable.role = { }, owned: null, namefrom: ['author'], + nameFromContent: true, context: null, unsupported: false, allowedElements: ['hr'] @@ -943,8 +952,11 @@ lookupTable.role = { unsupported: false, allowedElements: ['section'] }, + // https://www.w3.org/TR/dpub-aria-1.0/#doc-pullquote + // ==> (was 'none' now 'section') + // https://www.w3.org/TR/dpub-aria-1.1/#doc-pullquote 'doc-pullquote': { - type: 'none', + type: 'section', attributes: { allowed: ['aria-expanded'] }, @@ -972,6 +984,7 @@ lookupTable.role = { }, owned: null, namefrom: ['author'], + nameFromContent: true, context: null, unsupported: false, allowedElements: { @@ -1163,7 +1176,7 @@ lookupTable.role = { }, nameFrom: ['author'], context: null, - implicit: ['ol', 'ul', 'dl'], + implicit: ['ol', 'ul'], // DAISY-AXE: remove 'dl' which has no implicit role, see https://www.w3.org/TR/html-aria/#docconformance unsupported: false }, listbox: { @@ -1202,7 +1215,7 @@ lookupTable.role = { owned: null, nameFrom: ['author', 'contents'], context: ['list'], - implicit: ['li', 'dt'], + implicit: ['li'], // DAISY-AXE: remove 'dt' which has implicit 'term' role, see https://www.w3.org/TR/html-aria/#docconformance unsupported: false }, log: { @@ -1944,7 +1957,7 @@ lookupTable.role = { owned: null, nameFrom: ['author', 'contents'], context: null, - implicit: ['dt'], + implicit: ['dt', 'dfn'], // DAISY-AXE: add 'dfn' which has implicit 'term' role, see https://www.w3.org/TR/html-aria/#docconformance unsupported: false }, textbox: { diff --git a/lib/core/base/audit.js b/lib/core/base/audit.js index 3353545af1..d284e23a1a 100644 --- a/lib/core/base/audit.js +++ b/lib/core/base/audit.js @@ -778,11 +778,12 @@ function createIncompleteErrorResult(rule, error) { * For all the rules, create the helpUrl and add it to the data for that rule */ function getHelpUrl({ brand, application, lang }, ruleId, version) { + var _v = version ? version : axe.version.replace(/-\w+\.\w+$/, ''); return ( constants.helpUrlBase + brand + '/' + - (version || axe.version.substring(0, axe.version.lastIndexOf('.'))) + + (version || _v.substring(0, _v.lastIndexOf('.'))) + '/' + ruleId + '?application=' + diff --git a/lib/core/public/configure.js b/lib/core/public/configure.js index afe73582b1..76320e8dd5 100644 --- a/lib/core/public/configure.js +++ b/lib/core/public/configure.js @@ -3,6 +3,7 @@ import { configureStandards } from '../../standards'; import constants from '../constants'; function configure(spec) { + // throw new Error("DAISY-AXE BREAKPOINT AXE CONFIGURE"); const audit = axe._audit; if (!audit) { @@ -52,7 +53,6 @@ function configure(spec) { spec.checks.forEach(check => { if (!check.id) { throw new TypeError( - // eslint-disable-next-line max-len `Configured check ${JSON.stringify( check )} is invalid. Checks must be an object with at least an id property` @@ -72,7 +72,6 @@ function configure(spec) { spec.rules.forEach(rule => { if (!rule.id) { throw new TypeError( - // eslint-disable-next-line max-len `Configured rule ${JSON.stringify( rule )} is invalid. Rules must be an object with at least an id property` diff --git a/lib/rules/epub-type-has-matching-role-matches.js b/lib/rules/epub-type-has-matching-role-matches.js new file mode 100644 index 0000000000..21d542169f --- /dev/null +++ b/lib/rules/epub-type-has-matching-role-matches.js @@ -0,0 +1,19 @@ +function epubTypeHasMatchingRoleMatches(node) { + // selector: '[*|type]', + return ( + node.hasAttributeNS('http://www.idpf.org/2007/ops', 'type') || + node.hasAttribute('epub:type') // for unit tests that are not XML-aware due to fixture.innerHTML + ); + + // console.log('node.nodeName: ', node.nodeName); + // const attrs = Array.from(getNodeAttributes(node)); + // console.log(attrs.length); + // attrs.forEach((attr) => { + // console.log('\n====='); + // console.log(JSON.stringify(attr)); + // console.log('attr.nodeName: ', attr.nodeName); + // console.log('attr.namespaceURI: ', attr.namespaceURI); + // }); +} + +export default epubTypeHasMatchingRoleMatches; diff --git a/lib/rules/epub-type-has-matching-role.json b/lib/rules/epub-type-has-matching-role.json new file mode 100644 index 0000000000..f0f260a906 --- /dev/null +++ b/lib/rules/epub-type-has-matching-role.json @@ -0,0 +1,13 @@ +{ + "id": "epub-type-has-matching-role", + "impact": "moderate", + "matches": "epub-type-has-matching-role-matches", + "tags": ["cat.aria", "best-practice"], + "metadata": { + "description": "Ensure the element has an ARIA role matching its epub:type", + "help": "ARIA role should be used in addition to epub:type" + }, + "all": [], + "any": ["matching-aria-role"], + "none": [] +} diff --git a/lib/rules/landmark-one-main.json b/lib/rules/landmark-one-main.json index 34584d5b3d..cfb38a6eef 100644 --- a/lib/rules/landmark-one-main.json +++ b/lib/rules/landmark-one-main.json @@ -7,7 +7,7 @@ "description": "Ensure the document has a main landmark", "help": "Document should have one main landmark" }, - "all": ["page-has-main"], + "all": ["page-no-duplicate-main"], "any": [], "none": [] } diff --git a/lib/rules/landmark-unique-matches.js b/lib/rules/landmark-unique-matches.js index d651949764..ec213912a9 100644 --- a/lib/rules/landmark-unique-matches.js +++ b/lib/rules/landmark-unique-matches.js @@ -1,7 +1,8 @@ import { isVisibleToScreenReaders } from '../commons/dom'; -import { getRole } from '../commons/aria'; +import { getRole, getRoleType } from '../commons/aria'; import { getAriaRolesByType } from '../commons/standards'; import { accessibleTextVirtual } from '../commons/text'; +// import { closest } from '../core/utils'; export default function landmarkUniqueMatches(node, virtualNode) { return ( @@ -11,7 +12,7 @@ export default function landmarkUniqueMatches(node, virtualNode) { function isLandmarkVirtual(vNode) { const landmarkRoles = getAriaRolesByType('landmark'); - const role = getRole(vNode); + const role = getRole(vNode, { dpub: true }); if (!role) { return false; } @@ -23,5 +24,11 @@ function isLandmarkVirtual(vNode) { return !!accessibleText; } - return landmarkRoles.indexOf(role) >= 0 || role === 'region'; + var roleType = getRoleType(role); + return ( + role === 'region' || + roleType === 'landmark' || + landmarkRoles.includes(roleType) || + landmarkRoles.indexOf(role) >= 0 + ); } diff --git a/lib/rules/pagebreak-label-matches.js b/lib/rules/pagebreak-label-matches.js new file mode 100644 index 0000000000..0feaab4710 --- /dev/null +++ b/lib/rules/pagebreak-label-matches.js @@ -0,0 +1,16 @@ +function pagebreakLabelMatches(node) { + // selector: '[*|type~="pagebreak"], [role~="doc-pagebreak"]', + return ( + (node.hasAttribute('role') && + node.getAttribute('role').match(/\S+/g).includes('doc-pagebreak')) || + (node.hasAttributeNS('http://www.idpf.org/2007/ops', 'type') && + node + .getAttributeNS('http://www.idpf.org/2007/ops', 'type') + .match(/\S+/g) + .includes('pagebreak')) || + (node.hasAttribute('epub:type') && + node.getAttribute('epub:type').match(/\S+/g).includes('pagebreak')) // for unit tests that are not XML-aware due to fixture.innerHTML + ); +} + +export default pagebreakLabelMatches; diff --git a/lib/rules/pagebreak-label.json b/lib/rules/pagebreak-label.json new file mode 100644 index 0000000000..521bb81fce --- /dev/null +++ b/lib/rules/pagebreak-label.json @@ -0,0 +1,18 @@ +{ + "id": "pagebreak-label", + "impact": "moderate", + "matches": "pagebreak-label-matches", + "tags": ["cat.epub"], + "metadata": { + "description": "Ensure page markers have an accessible label", + "help": "Page markers must have an accessible label" + }, + "all": [], + "any": [ + "aria-label", + "aria-labelledby", + "non-empty-title", + "has-visible-text" + ], + "none": [] +} diff --git a/lib/standards/dpub-roles.js b/lib/standards/dpub-roles.js index c2f8d33b95..bd1b88ecbe 100644 --- a/lib/standards/dpub-roles.js +++ b/lib/standards/dpub-roles.js @@ -27,7 +27,7 @@ const dpubRoles = { superclassRole: ['link'] }, 'doc-biblioentry': { - type: 'listitem', + type: 'structure', allowedAttrs: [ 'aria-expanded', 'aria-level', @@ -36,11 +36,15 @@ const dpubRoles = { ], superclassRole: ['listitem'], deprecated: true + // requiredContext: ['doc-bibliography'] + // requiredContext: ['list', 'listitem'] }, 'doc-bibliography': { type: 'landmark', allowedAttrs: ['aria-expanded'], superclassRole: ['landmark'] + // requiredOwned: ['doc-biblioentry'] + // requiredOwned: ['list'] }, 'doc-biblioref': { type: 'link', @@ -84,20 +88,24 @@ const dpubRoles = { superclassRole: ['section'] }, 'doc-endnote': { - type: 'listitem', + type: 'structure', allowedAttrs: [ 'aria-expanded', 'aria-level', 'aria-posinset', 'aria-setsize' ], - superclassRole: ['listitem'], + superclassRole: ['none'], deprecated: true + // requiredContext: ['doc-endnotes'] + // requiredContext: ['list', 'listitem'] }, 'doc-endnotes': { type: 'landmark', allowedAttrs: ['aria-expanded'], superclassRole: ['landmark'] + // requiredOwned: ['doc-endnote'] + // requiredOwned: ['list'] }, 'doc-epigraph': { type: 'section', @@ -114,10 +122,13 @@ const dpubRoles = { allowedAttrs: ['aria-expanded'], superclassRole: ['landmark'] }, + // https://www.w3.org/TR/dpub-aria-1.0/#doc-example + // ==> (was 'section' now 'figure') + // https://www.w3.org/TR/dpub-aria-1.1/#doc-example 'doc-example': { - type: 'section', + type: 'structure', allowedAttrs: ['aria-expanded'], - superclassRole: ['section'] + superclassRole: ['figure'] }, 'doc-footnote': { type: 'section', @@ -187,8 +198,11 @@ const dpubRoles = { allowedAttrs: ['aria-expanded'], superclassRole: ['landmark'] }, + // https://www.w3.org/TR/dpub-aria-1.0/#doc-pullquote + // ==> (was 'none' now 'section') + // https://www.w3.org/TR/dpub-aria-1.1/#doc-pullquote 'doc-pullquote': { - type: 'none', + type: 'section', superclassRole: ['none'] }, 'doc-qna': { diff --git a/locales/_template.json b/locales/_template.json index d1993f0d82..481bfda8dd 100644 --- a/locales/_template.json +++ b/locales/_template.json @@ -173,6 +173,10 @@ "description": "Ensure table headers have discernible text", "help": "Table header text should not be empty" }, + "epub-type-has-matching-role": { + "description": "Ensure the element has an ARIA role matching its epub:type", + "help": "ARIA role should be used in addition to epub:type" + }, "focus-order-semantics": { "description": "Ensure elements in the focus order have a role appropriate for interactive content", "help": "Elements in the focus order should have an appropriate role" @@ -341,6 +345,10 @@ "description": "Ensure that the page, or at least one of its frames contains a level-one heading", "help": "Page should contain a level-one heading" }, + "pagebreak-label": { + "description": "Ensure page markers have an accessible label", + "help": "Page markers must have an accessible label" + }, "presentation-role-conflict": { "description": "Ensure elements marked as presentational do not have global ARIA or tabindex so that all screen readers ignore them", "help": "Elements marked as presentational should be consistently ignored" @@ -596,6 +604,10 @@ "pass": "Element is focusable.", "fail": "Element is not focusable." }, + "matching-aria-role": { + "pass": "Element has an ARIA role matching its epub:type", + "fail": "Element has no ARIA role matching its epub:type" + }, "no-implicit-explicit-label": { "pass": "There is no mismatch between a