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)
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