Skip to content

Commit

Permalink
chore(release): v4.9.1 (#4446)
Browse files Browse the repository at this point in the history
###
[4.9.1](v4.9.0...v4.9.1)
(2024-05-06)

### Bug Fixes

- Prevent errors when loading axe in a page with prototype.js
- **aria-allowed-attr:** allow meter role allowed aria-\* attributes on
meter element
([#4435](#4435))
([7ac6392](7ac6392))
- **aria-allowed-role:** add gridcell, separator, slider and treeitem to
allowed roles of button element
([#4398](#4398))
([4788bf8](4788bf8))
- **aria-roles:** correct abstract roles (types) for
aria-roles([#4421](#4421))
- **aria-valid-attr-value:** aria-controls & aria-haspopup incomplete
([#4418](#4418))
- fix building axe-core translation files with region locales
([#4396](#4396))
([5c318f3](5c318f3)),
closes [#4388](#4388)
- **invalidrole:** allow upper and mixed case role names
([#4358](#4358))
([105016c](105016c)),
closes [#2695](#2695)
- **isVisibleOnScreen:** account for position: absolute elements inside
overflow container
([#4405](#4405))
([2940f6e](2940f6e)),
closes [#4016](#4016)
- **label-content-name-mismatch:** better dismiss and wysiwyg symbolic
text characters
([#4402](#4402))
- **region:** Decorative images ignored by region rule
([#4412](#4412))
- **target-size:** ignore descendant elements in shadow dom
([#4410](#4410))
([6091367](6091367))
- **target-size:** pass for element that has nearby elements that are
obscured ([#4422](#4422))
([3a90bb7](3a90bb7)),
closes [#4387](#4387)


This PR was opened by a robot 🤖 🎉 (And updated by @WilcoFiers
)
  • Loading branch information
WilcoFiers committed May 6, 2024
2 parents d847924 + 0f2b8ac commit fff9b9d
Show file tree
Hide file tree
Showing 74 changed files with 10,032 additions and 1,244 deletions.
28 changes: 26 additions & 2 deletions .eslintrc.js
Expand Up @@ -59,13 +59,37 @@ module.exports = {
selector: 'MemberExpression[property.name=tagName]',
message: "Don't use node.tagName, use node.nodeName instead."
},
// node.attributes can be clobbered so is unsafe to use
// @see https://github.com/dequelabs/axe-core/pull/1432
{
// node.attributes can be clobbered so is unsafe to use
// @see https://github.com/dequelabs/axe-core/pull/1432
// node.attributes
selector:
'MemberExpression[object.name=node][property.name=attributes]',
message:
"Don't use node.attributes, use node.hasAttributes() or axe.utils.getNodeAttributes(node) instead."
},
{
// vNode.actualNode.attributes
selector:
'MemberExpression[object.property.name=actualNode][property.name=attributes]',
message:
"Don't use node.attributes, use node.hasAttributes() or axe.utils.getNodeAttributes(node) instead."
},
// node.contains doesn't work with shadow dom
// @see https://github.com/dequelabs/axe-core/issues/4194
{
// node.contains()
selector:
'CallExpression[callee.object.name=node][callee.property.name=contains]',
message:
"Don't use node.contains(node2) as it doesn't work across shadow DOM. Use axe.utils.contains(node, node2) instead."
},
{
// vNode.actualNode.contains()
selector:
'CallExpression[callee.object.property.name=actualNode][callee.property.name=contains]',
message:
"Don't use node.contains(node2) as it doesn't work across shadow DOM. Use axe.utils.contains(node, node2) instead."
}
]
},
Expand Down
18 changes: 18 additions & 0 deletions .vscode/launch.json
@@ -0,0 +1,18 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "attach",
"name": "Attach to Karma test:debug",
"address": "localhost",
"port": 9765, // keep in sync with debugPort in karma.conf.js
"webRoot": "${workspaceFolder}",
"sourceMaps": true,
"sourceMapPathOverrides": {
"*": "${webRoot}/*",
"base/*": "${webRoot}/*"
}
}
]
}
17 changes: 17 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,23 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### [4.9.1](https://github.com/dequelabs/axe-core/compare/v4.9.0...v4.9.1) (2024-05-06)

### Bug Fixes

- Prevent errors when loading axe in a page with prototype.js
- **aria-allowed-attr:** allow meter role allowed aria-\* attributes on meter element ([#4435](https://github.com/dequelabs/axe-core/issues/4435)) ([7ac6392](https://github.com/dequelabs/axe-core/commit/7ac63921e7fab21f3359dcfc8affa7585bc9c25b))
- **aria-allowed-role:** add gridcell, separator, slider and treeitem to allowed roles of button element ([#4398](https://github.com/dequelabs/axe-core/issues/4398)) ([4788bf8](https://github.com/dequelabs/axe-core/commit/4788bf8d6fd963d7b017dad950b122ffcea8d151))
- **aria-roles:** correct abstract roles (types) for aria-roles([#4421](https://github.com/dequelabs/axe-core/pull/4421))
- **aria-valid-attr-value:** aria-controls & aria-haspopup incomplete ([#4418](https://github.com/dequelabs/axe-core/pull/4418))
- fix building axe-core translation files with region locales ([#4396](https://github.com/dequelabs/axe-core/issues/4396)) ([5c318f3](https://github.com/dequelabs/axe-core/commit/5c318f3537056be5779cb53374bc6f4785947c91)), closes [#4388](https://github.com/dequelabs/axe-core/issues/4388)
- **invalidrole:** allow upper and mixed case role names ([#4358](https://github.com/dequelabs/axe-core/issues/4358)) ([105016c](https://github.com/dequelabs/axe-core/commit/105016cfe9d82876cfed2ff5c656a7842c5b3761)), closes [#2695](https://github.com/dequelabs/axe-core/issues/2695)
- **isVisibleOnScreen:** account for position: absolute elements inside overflow container ([#4405](https://github.com/dequelabs/axe-core/issues/4405)) ([2940f6e](https://github.com/dequelabs/axe-core/commit/2940f6ee36ba52d8cf089be2a3c8e7c516c81dd6)), closes [#4016](https://github.com/dequelabs/axe-core/issues/4016)
- **label-content-name-mismatch:** better dismiss and wysiwyg symbolic text characters ([#4402](https://github.com/dequelabs/axe-core/issues/4402))
- **region:** Decorative images ignored by region rule ([#4412](https://github.com/dequelabs/axe-core/pull/4412))
- **target-size:** ignore descendant elements in shadow dom ([#4410](https://github.com/dequelabs/axe-core/issues/4410)) ([6091367](https://github.com/dequelabs/axe-core/commit/6091367a20f70e536fc7e8d77eae4fa7232bc7c0))
- **target-size:** pass for element that has nearby elements that are obscured ([#4422](https://github.com/dequelabs/axe-core/issues/4422)) ([3a90bb7](https://github.com/dequelabs/axe-core/commit/3a90bb70c8db087b2f03cc30a4aee756995c311c)), closes [#4387](https://github.com/dequelabs/axe-core/issues/4387)

## [4.9.0](https://github.com/dequelabs/axe-core/compare/v4.8.4...v4.9.0) (2024-03-25)

### Features
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Expand Up @@ -162,7 +162,7 @@ If you need to debug the unit tests in a browser, you can run:
npm run test:debug
```

This will start the Karma server and open up the Chrome browser. Click the `Debug` button to start debugging the tests. You can also navigate to the listed URL in your browser of choice to debug tests using that browser.
This will start the Karma server and open up the Chrome browser. Click the `Debug` button to start debugging the tests. You can either use that browser's debugger or attach an external debugger on port 9765; [a VS Code launch profile](./.vscode/launch.json) is provided. You can also navigate to the listed URL in your browser of choice to debug tests using that browser.

Because the amount of tests is so large, it's recommended to debug only a specific set of unit tests rather than the whole test suite. You can use the `testDirs` argument when using the debug command and pass a specific test directory. The test directory names are the same as those used for `test:unit:*`:

Expand Down
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -103,9 +103,9 @@ or equivalently:

This will create a new build for axe, called `axe.<lang>.js` and `axe.<lang>.min.js`. If you want to build all localized versions, simply pass in `--all-lang` instead. If you want to build multiple localized versions (but not all of them), you can pass in a comma-separated list of languages to the `--lang` flag, like `--lang=nl,ja`.

To create a new translation for axe, start by running `grunt translate --lang=<langcode>`. This will create a json file fin the `./locales` directory, with the default English text in it for you to translate. Alternatively, you could copy `./locales/_template.json`. We welcome any localization for axe-core. For details on how to contribute, see the Contributing section below. For details on the message syntax, see [Check Message Template](/doc/check-message-template.md).
To create a new translation for axe, start by running `grunt translate --lang=<langcode>`. This will create a json file in the `./locales` directory, with the default English text in it for you to translate. Alternatively, you could copy `./locales/_template.json`. We welcome any localization for axe-core. For details on how to contribute, see the Contributing section below. For details on the message syntax, see [Check Message Template](/doc/check-message-template.md).

To update existing translation file, re-run `grunt translate --lang=<langcode>`. This will add new messages used in English and remove messages which were not used in English.
To update an existing translation file, re-run `grunt translate --lang=<langcode>`. This will add new messages used in English and remove messages which were not used in English.

Additionally, locale can be applied at runtime by passing a `locale` object to `axe.configure()`. The locale object must be of the same shape as existing locales in the `./locales` directory. For example:

Expand Down
2 changes: 1 addition & 1 deletion bower.json
@@ -1,6 +1,6 @@
{
"name": "axe-core",
"version": "4.9.0",
"version": "4.9.1",
"deprecated": true,
"contributors": [
{
Expand Down
15 changes: 12 additions & 3 deletions build/rule-generator/questions.js
Expand Up @@ -33,7 +33,10 @@ const validateGetRuleName = async input => {
throw new Error(`RULE name conflicts with an existing rule's filename.`);
}
// 3) ensure no rule id overlaps
const ruleSpecs = await glob(`${directories.rules}/**/*.json`);
const ruleSpecs = await glob(`${directories.rules}/**/*.json`, {
posix: true,
absolute: true
});
const axeRulesIds = ruleSpecs.reduce((out, specPath) => {
const spec = require(specPath);
out.push(spec.id);
Expand Down Expand Up @@ -62,7 +65,10 @@ const validateGetCheckName = async input => {
);
}
// 2) ensure no check filename overlaps
const checkSpecs = await glob(`${directories.checks}/**/*.json`);
const checkSpecs = await glob(`${directories.checks}/**/*.json`, {
posix: true,
absolute: true
});
// cannot use `fs.existsSync` here, as we do not know which category of checks to look under
const axeChecksFileNames = checkSpecs.map(
f => f.replace('.json', '').split('/').reverse()[0]
Expand All @@ -71,7 +77,10 @@ const validateGetCheckName = async input => {
throw new Error('CHECK name conflicts with an existing filename.');
}
// 3) ensure no check id overlaps
const ruleSpecs = await glob(`${directories.rules}/**/*.json`);
const ruleSpecs = await glob(`${directories.rules}/**/*.json`, {
posix: true,
absolute: true
});
const axe = require(directories.axePath);
const axeChecksIds = ruleSpecs.reduce((out, specPath) => {
const spec = require(specPath);
Expand Down
8 changes: 5 additions & 3 deletions build/tasks/configure.js
Expand Up @@ -18,9 +18,11 @@ module.exports = function (grunt) {
});

this.files.forEach(function (file) {
const match = file.dest.auto.match(/\.([a-z]{2,3})\.js/);
if (match) {
options.locale = match[1];
// locale will always be the 2nd to last part of the
// filename and in the format of "<name>.<locale>.js"
const parts = file.dest.auto.split('.');
if (parts.length > 2) {
options.locale = parts[parts.length - 2];
}

buildRules(grunt, options, null, function (result) {
Expand Down
2 changes: 1 addition & 1 deletion build/tasks/metadata-function-map.js
Expand Up @@ -22,7 +22,7 @@ module.exports = function (grunt) {
'// This file is automatically generated using build/tasks/metadata-function-map.js\n';

src.forEach(globPath => {
glob.sync(globPath).forEach(filePath => {
glob.sync(globPath, { posix: true }).forEach(filePath => {
const relativePath = path.relative(
path.dirname(file.dest),
filePath
Expand Down
4 changes: 2 additions & 2 deletions doc/developer-guide.md
Expand Up @@ -31,7 +31,7 @@ Axe 3.0 supports open Shadow DOM: see our virtual DOM APIs and test utilities fo

### Environment Pre-requisites

1. You must have NodeJS version 12 or higher installed.
1. You must have NodeJS version 18 or higher installed.
1. Install npm development dependencies. In the root folder of your axe-core repository, run `npm install`

### Building axe.js
Expand Down Expand Up @@ -71,7 +71,7 @@ There are also a set of tests that are not considered unit tests that you can ru

Additionally, you can [watch for changes](#watching-for-changes) to files and automatically run the relevant tests.

If you need to debug a test in a non-headless browser, you can run `npm run test:debug` which will run the Karma tests in non-headless Chrome. You can also navigate to the newly opened page using any supported browser.
If you need to debug a test in a non-headless browser, you can run `npm run test:debug` which will run the Karma tests in non-headless Chrome. You can either use that browser's debugger or attach an external debugger on port 9765; [a VS Code launch profile](../.vscode/launch.json) is provided. You can also navigate to the newly opened page using any supported browser.

You can scope which set of tests to debug by passing the `testDirs` argument. Supported values are:

Expand Down
6 changes: 4 additions & 2 deletions doc/standards-object.md
Expand Up @@ -67,9 +67,11 @@ The [`ariaRoles`](../lib/standards/aria-roles.js) object defines valid ARIA role

- `type` - string(required). [The role type](https://www.w3.org/TR/wai-aria-1.1/#roles_categorization). Valid types are:
- `abstract`
- `widget`
- `structure`
- `composite`
- `landmark`
- `structure`
- `widget`
- `window`
- `requiredContext` - array(optional). List of required parent roles.
- `requiredOwned` - array(optional). List of required owned roles.
- `requiredAttrs` - array(optional). List of required attributes.
Expand Down
15 changes: 13 additions & 2 deletions lib/checks/aria/aria-valid-attr-value-evaluate.js
Expand Up @@ -36,12 +36,23 @@ export default function ariaValidAttrValueEvaluate(node, options, virtualNode) {

const preChecks = {
// aria-controls should only check if element exists if the element
// doesn't have aria-expanded=false or aria-selected=false (tabs)
// doesn't have aria-expanded=false, aria-selected=false (tabs),
// or aria-haspopup (may load later)
// @see https://github.com/dequelabs/axe-core/issues/1463
// @see https://github.com/dequelabs/axe-core/issues/4363
'aria-controls': () => {
const hasPopup =
['false', null].includes(virtualNode.attr('aria-haspopup')) === false;

if (hasPopup) {
needsReview = `aria-controls="${virtualNode.attr('aria-controls')}"`;
messageKey = 'controlsWithinPopup';
}

return (
virtualNode.attr('aria-expanded') !== 'false' &&
virtualNode.attr('aria-selected') !== 'false'
virtualNode.attr('aria-selected') !== 'false' &&
hasPopup === false
);
},
// aria-current should mark as needs review if any value is used that is
Expand Down
3 changes: 2 additions & 1 deletion lib/checks/aria/aria-valid-attr-value.json
Expand Up @@ -15,7 +15,8 @@
"noIdShadow": "ARIA attribute element ID does not exist on the page or is a descendant of a different shadow DOM tree: ${data.needsReview}",
"ariaCurrent": "ARIA attribute value is invalid and will be treated as \"aria-current=true\": ${data.needsReview}",
"idrefs": "Unable to determine if ARIA attribute element ID exists on the page: ${data.needsReview}",
"empty": "ARIA attribute value is ignored while empty: ${data.needsReview}"
"empty": "ARIA attribute value is ignored while empty: ${data.needsReview}",
"controlsWithinPopup": "Unable to determine if aria-controls referenced ID exists on the page while using aria-haspopup: ${data.needsReview}"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/checks/aria/invalidrole-evaluate.js
Expand Up @@ -28,7 +28,7 @@ import { tokenList } from '../../core/utils';
function invalidroleEvaluate(node, options, virtualNode) {
const allRoles = tokenList(virtualNode.attr('role'));
const allInvalid = allRoles.every(
role => !isValidRole(role, { allowAbstract: true })
role => !isValidRole(role.toLowerCase(), { allowAbstract: true })
);

/**
Expand Down
7 changes: 6 additions & 1 deletion lib/checks/aria/valid-scrollable-semantics-evaluate.js
Expand Up @@ -16,16 +16,21 @@ const VALID_TAG_NAMES_FOR_SCROLLABLE_REGIONS = {
* appropriate for scrollable elements found in the focus order.
*/
const VALID_ROLES_FOR_SCROLLABLE_REGIONS = {
alert: true,
alertdialog: true,
application: true,
article: true,
banner: false,
complementary: true,
contentinfo: true,
dialog: true,
form: true,
log: true,
main: true,
navigation: true,
region: true,
search: false
search: false,
status: true
};

/**
Expand Down
15 changes: 6 additions & 9 deletions lib/checks/label/label-content-name-mismatch-evaluate.js
Expand Up @@ -41,12 +41,7 @@ function labelContentNameMismatchEvaluate(node, options, virtualNode) {
const pixelThreshold = options?.pixelThreshold;
const occurrenceThreshold =
options?.occurrenceThreshold ?? options?.occuranceThreshold;

const accText = accessibleText(node).toLowerCase();
if (isHumanInterpretable(accText) < 1) {
return undefined;
}

const visibleText = sanitize(
subtreeText(virtualNode, {
subtreeDescendant: true,
Expand All @@ -55,13 +50,15 @@ function labelContentNameMismatchEvaluate(node, options, virtualNode) {
occurrenceThreshold
})
).toLowerCase();

if (!visibleText) {
return true;
}
if (isHumanInterpretable(visibleText) < 1) {
if (isStringContained(visibleText, accText)) {
return true;
}

if (
isHumanInterpretable(accText) < 1 ||
isHumanInterpretable(visibleText) < 1
) {
return undefined;
}

Expand Down
23 changes: 21 additions & 2 deletions lib/checks/mobile/target-offset-evaluate.js
Expand Up @@ -20,8 +20,27 @@ export default function targetOffsetEvaluate(node, options, vNode) {
continue;
}
// the offset code works off radius but we want our messaging to reflect diameter
const offset =
roundToSingleDecimal(getOffset(vNode, vNeighbor, minOffset / 2)) * 2;
let offset = null;
try {
offset = getOffset(vNode, vNeighbor, minOffset / 2);
} catch (err) {
if (err.message.startsWith('splitRects')) {
this.data({
messageKey: 'tooManyRects',
closestOffset: 0,
minOffset
});
return undefined;
}

throw err;
}

if (offset === null) {
continue;
}

offset = roundToSingleDecimal(offset) * 2;
if (offset + roundingMargin >= minOffset) {
continue;
}
Expand Down
3 changes: 2 additions & 1 deletion lib/checks/mobile/target-offset.json
Expand Up @@ -14,7 +14,8 @@
"fail": "Target has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px.",
"incomplete": {
"default": "Element with negative tabindex has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px. Is this a target?",
"nonTabbableNeighbor": "Target has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px. Is the neighbor a target?"
"nonTabbableNeighbor": "Target has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px. Is the neighbor a target?",
"tooManyRects": "Could not get the target size because there are too many overlapping elements"
}
}
}
Expand Down
11 changes: 6 additions & 5 deletions lib/checks/mobile/target-size-evaluate.js
Expand Up @@ -5,6 +5,7 @@ import {
rectHasMinimumSize,
hasVisualOverlap
} from '../../commons/math';
import { contains } from '../../core/utils';

/**
* Determine if an element has a minimum size, taking into account
Expand Down Expand Up @@ -131,8 +132,10 @@ function getLargestUnobscuredArea(vNode, obscuredNodes) {
const obscuringRects = obscuredNodes.map(
({ boundingClientRect: rect }) => rect
);
const unobscuredRects = splitRects(nodeRect, obscuringRects);
if (unobscuredRects.length === 0) {
let unobscuredRects;
try {
unobscuredRects = splitRects(nodeRect, obscuringRects);
} catch (err) {
return null;
}

Expand Down Expand Up @@ -185,9 +188,7 @@ function toDecimalSize(rect) {
}

function isDescendantNotInTabOrder(vAncestor, vNode) {
return (
vAncestor.actualNode.contains(vNode.actualNode) && !isInTabOrder(vNode)
);
return contains(vAncestor, vNode) && !isInTabOrder(vNode);
}

function mapActualNodes(vNodes) {
Expand Down

0 comments on commit fff9b9d

Please sign in to comment.