From cda783b78ae155067e15f7825bf0b936f693c857 Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Tue, 6 Jan 2026 13:41:26 -0700 Subject: [PATCH 01/62] ci: fix release script (#4984) Our first [GHA GitHub release failed](https://github.com/dequelabs/axe-core/actions/runs/20758308118/job/59607316910). Looks like the script expects the env which wasn't set. --- .github/workflows/deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index fda5a9dec..867f60a82 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -173,6 +173,8 @@ jobs: - name: Make Release Script Executable run: chmod +x ./node-github-release.sh - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: ./node-github-release.sh validate-deploy: name: Validate Deployment From 8541f2cf3d437ad4aee3881592469042a027aff1 Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Tue, 13 Jan 2026 08:04:41 -0700 Subject: [PATCH 02/62] test: fix test:unit (#4989) Running `npm run test:unit` results in loads of failures. After racking my brain about why, I remembered I had a similar problem in my [fixing `npm run test:debug` pr](https://github.com/dequelabs/axe-core/pull/4769) where resetting the memoize functions results in them not being able to clear the cache between tests. Turns out that was the issue here. Note: this isn't a problem in our pr runs or nightly tests because we either call `npm run: test:unit:` directly or run `npm test` which runs all the tests in turn. This only happens when running `npm run test:unit` since the browser doesn't close between test types. --- test/core/utils/memoize.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/core/utils/memoize.js b/test/core/utils/memoize.js index 7c8503361..ec3e3baee 100644 --- a/test/core/utils/memoize.js +++ b/test/core/utils/memoize.js @@ -2,9 +2,9 @@ describe('axe.utils.memoize', function () { 'use strict'; it('should add the function to axe._memoizedFns', function () { - axe._memoizedFns.length = 0; + const length = axe._memoizedFns.length; axe.utils.memoize(function myFn() {}); - assert.equal(axe._memoizedFns.length, 1); + assert.equal(axe._memoizedFns.length, length + 1); }); }); From 9e091391189dba452ea485275609120e1e6ae8ba Mon Sep 17 00:00:00 2001 From: Chris Hutchins Date: Fri, 23 Jan 2026 10:31:33 -0500 Subject: [PATCH 03/62] feat(rules): deprecate landmark-complementary-is-top-level rules (#4992) Deprecated landmark-complementary-is-top-level rules based on recent ARIA and HTML-AAM changes Closes: [#4950 ](https://github.com/dequelabs/axe-core/issues/4950) --- doc/rule-descriptions.md | 72 +++++++++---------- .../landmark-complementary-is-top-level.json | 3 +- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index 6858eb630..0b7997ee2 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -95,36 +95,35 @@ These rules are disabled by default, until WCAG 2.2 is more widely adopted and r Rules that do not necessarily conform to WCAG success criterion but are industry accepted practices that improve the user experience. -| Rule ID | Description | Impact | Tags | Issue Type | [ACT Rules](https://www.w3.org/WAI/standards-guidelines/act/rules/) | -| :------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :----------------------------------------------- | :------------------------- | :------------------------------------------------------------------ | -| [accesskeys](https://dequeuniversity.com/rules/axe/4.11/accesskeys?application=RuleDescription) | Ensure every accesskey attribute value is unique | Serious | cat.keyboard, best-practice | failure | | -| [aria-allowed-role](https://dequeuniversity.com/rules/axe/4.11/aria-allowed-role?application=RuleDescription) | Ensure role attribute has an appropriate value for the element | Minor | cat.aria, best-practice | failure, needs review | | -| [aria-dialog-name](https://dequeuniversity.com/rules/axe/4.11/aria-dialog-name?application=RuleDescription) | Ensure every ARIA dialog and alertdialog node has an accessible name | Serious | cat.aria, best-practice | failure, needs review | | -| [aria-text](https://dequeuniversity.com/rules/axe/4.11/aria-text?application=RuleDescription) | Ensure role="text" is used on elements with no focusable descendants | Serious | cat.aria, best-practice | failure, needs review | | -| [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 | | -| [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 | | -| [label-title-only](https://dequeuniversity.com/rules/axe/4.11/label-title-only?application=RuleDescription) | Ensure that every form element has a visible label and is not solely labeled using hidden labels, or the title or aria-describedby attributes | Serious | cat.forms, best-practice | failure | | -| [landmark-banner-is-top-level](https://dequeuniversity.com/rules/axe/4.11/landmark-banner-is-top-level?application=RuleDescription) | Ensure the banner landmark is at top level | Moderate | cat.semantics, best-practice | failure | | -| [landmark-complementary-is-top-level](https://dequeuniversity.com/rules/axe/4.11/landmark-complementary-is-top-level?application=RuleDescription) | Ensure the complementary landmark or aside is at top level | Moderate | cat.semantics, best-practice | failure | | -| [landmark-contentinfo-is-top-level](https://dequeuniversity.com/rules/axe/4.11/landmark-contentinfo-is-top-level?application=RuleDescription) | Ensure the contentinfo landmark is at top level | Moderate | cat.semantics, best-practice | failure | | -| [landmark-main-is-top-level](https://dequeuniversity.com/rules/axe/4.11/landmark-main-is-top-level?application=RuleDescription) | Ensure the main landmark is at top level | Moderate | cat.semantics, best-practice | failure | | -| [landmark-no-duplicate-banner](https://dequeuniversity.com/rules/axe/4.11/landmark-no-duplicate-banner?application=RuleDescription) | Ensure the document has at most one banner landmark | Moderate | cat.semantics, best-practice | failure | | -| [landmark-no-duplicate-contentinfo](https://dequeuniversity.com/rules/axe/4.11/landmark-no-duplicate-contentinfo?application=RuleDescription) | Ensure the document has at most one contentinfo landmark | Moderate | cat.semantics, best-practice | failure | | -| [landmark-no-duplicate-main](https://dequeuniversity.com/rules/axe/4.11/landmark-no-duplicate-main?application=RuleDescription) | Ensure the document has at most one main landmark | Moderate | cat.semantics, best-practice | failure | | -| [landmark-one-main](https://dequeuniversity.com/rules/axe/4.11/landmark-one-main?application=RuleDescription) | Ensure the document has a main landmark | Moderate | cat.semantics, best-practice | failure | | -| [landmark-unique](https://dequeuniversity.com/rules/axe/4.11/landmark-unique?application=RuleDescription) | Ensure landmarks are unique | Moderate | cat.semantics, best-practice | failure | | -| [meta-viewport-large](https://dequeuniversity.com/rules/axe/4.11/meta-viewport-large?application=RuleDescription) | Ensure <meta name="viewport"> can scale a significant amount | Minor | cat.sensory-and-visual-cues, best-practice | failure | | -| [page-has-heading-one](https://dequeuniversity.com/rules/axe/4.11/page-has-heading-one?application=RuleDescription) | Ensure that the page, or at least one of its frames contains a level-one heading | Moderate | cat.semantics, best-practice | failure | | -| [presentation-role-conflict](https://dequeuniversity.com/rules/axe/4.11/presentation-role-conflict?application=RuleDescription) | Ensure elements marked as presentational do not have global ARIA or tabindex so that all screen readers ignore them | Minor | cat.aria, best-practice, ACT | failure | [46ca7f](https://act-rules.github.io/rules/46ca7f) | -| [region](https://dequeuniversity.com/rules/axe/4.11/region?application=RuleDescription) | Ensure all page content is contained by landmarks | Moderate | cat.keyboard, best-practice, RGAAv4, RGAA-9.2.1 | failure | | -| [scope-attr-valid](https://dequeuniversity.com/rules/axe/4.11/scope-attr-valid?application=RuleDescription) | Ensure the scope attribute is used correctly on tables | Moderate | cat.tables, best-practice | failure | | -| [skip-link](https://dequeuniversity.com/rules/axe/4.11/skip-link?application=RuleDescription) | Ensure all skip links have a focusable target | Moderate | cat.keyboard, best-practice, RGAAv4, RGAA-12.7.1 | failure, needs review | | -| [tabindex](https://dequeuniversity.com/rules/axe/4.11/tabindex?application=RuleDescription) | Ensure tabindex attribute values are not greater than 0 | Serious | cat.keyboard, best-practice | failure | | -| [table-duplicate-name](https://dequeuniversity.com/rules/axe/4.11/table-duplicate-name?application=RuleDescription) | Ensure the <caption> element does not contain the same text as the summary attribute | Minor | cat.tables, best-practice, RGAAv4, RGAA-5.2.1 | failure, needs review | | +| Rule ID | Description | Impact | Tags | Issue Type | [ACT Rules](https://www.w3.org/WAI/standards-guidelines/act/rules/) | +| :-------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :----------------------------------------------- | :------------------------- | :------------------------------------------------------------------ | +| [accesskeys](https://dequeuniversity.com/rules/axe/4.11/accesskeys?application=RuleDescription) | Ensure every accesskey attribute value is unique | Serious | cat.keyboard, best-practice | failure | | +| [aria-allowed-role](https://dequeuniversity.com/rules/axe/4.11/aria-allowed-role?application=RuleDescription) | Ensure role attribute has an appropriate value for the element | Minor | cat.aria, best-practice | failure, needs review | | +| [aria-dialog-name](https://dequeuniversity.com/rules/axe/4.11/aria-dialog-name?application=RuleDescription) | Ensure every ARIA dialog and alertdialog node has an accessible name | Serious | cat.aria, best-practice | failure, needs review | | +| [aria-text](https://dequeuniversity.com/rules/axe/4.11/aria-text?application=RuleDescription) | Ensure role="text" is used on elements with no focusable descendants | Serious | cat.aria, best-practice | failure, needs review | | +| [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 | | +| [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 | | +| [label-title-only](https://dequeuniversity.com/rules/axe/4.11/label-title-only?application=RuleDescription) | Ensure that every form element has a visible label and is not solely labeled using hidden labels, or the title or aria-describedby attributes | Serious | cat.forms, best-practice | failure | | +| [landmark-banner-is-top-level](https://dequeuniversity.com/rules/axe/4.11/landmark-banner-is-top-level?application=RuleDescription) | Ensure the banner landmark is at top level | Moderate | cat.semantics, best-practice | failure | | +| [landmark-contentinfo-is-top-level](https://dequeuniversity.com/rules/axe/4.11/landmark-contentinfo-is-top-level?application=RuleDescription) | Ensure the contentinfo landmark is at top level | Moderate | cat.semantics, best-practice | failure | | +| [landmark-main-is-top-level](https://dequeuniversity.com/rules/axe/4.11/landmark-main-is-top-level?application=RuleDescription) | Ensure the main landmark is at top level | Moderate | cat.semantics, best-practice | failure | | +| [landmark-no-duplicate-banner](https://dequeuniversity.com/rules/axe/4.11/landmark-no-duplicate-banner?application=RuleDescription) | Ensure the document has at most one banner landmark | Moderate | cat.semantics, best-practice | failure | | +| [landmark-no-duplicate-contentinfo](https://dequeuniversity.com/rules/axe/4.11/landmark-no-duplicate-contentinfo?application=RuleDescription) | Ensure the document has at most one contentinfo landmark | Moderate | cat.semantics, best-practice | failure | | +| [landmark-no-duplicate-main](https://dequeuniversity.com/rules/axe/4.11/landmark-no-duplicate-main?application=RuleDescription) | Ensure the document has at most one main landmark | Moderate | cat.semantics, best-practice | failure | | +| [landmark-one-main](https://dequeuniversity.com/rules/axe/4.11/landmark-one-main?application=RuleDescription) | Ensure the document has a main landmark | Moderate | cat.semantics, best-practice | failure | | +| [landmark-unique](https://dequeuniversity.com/rules/axe/4.11/landmark-unique?application=RuleDescription) | Ensure landmarks are unique | Moderate | cat.semantics, best-practice | failure | | +| [meta-viewport-large](https://dequeuniversity.com/rules/axe/4.11/meta-viewport-large?application=RuleDescription) | Ensure <meta name="viewport"> can scale a significant amount | Minor | cat.sensory-and-visual-cues, best-practice | failure | | +| [page-has-heading-one](https://dequeuniversity.com/rules/axe/4.11/page-has-heading-one?application=RuleDescription) | Ensure that the page, or at least one of its frames contains a level-one heading | Moderate | cat.semantics, best-practice | failure | | +| [presentation-role-conflict](https://dequeuniversity.com/rules/axe/4.11/presentation-role-conflict?application=RuleDescription) | Ensure elements marked as presentational do not have global ARIA or tabindex so that all screen readers ignore them | Minor | cat.aria, best-practice, ACT | failure | [46ca7f](https://act-rules.github.io/rules/46ca7f) | +| [region](https://dequeuniversity.com/rules/axe/4.11/region?application=RuleDescription) | Ensure all page content is contained by landmarks | Moderate | cat.keyboard, best-practice, RGAAv4, RGAA-9.2.1 | failure | | +| [scope-attr-valid](https://dequeuniversity.com/rules/axe/4.11/scope-attr-valid?application=RuleDescription) | Ensure the scope attribute is used correctly on tables | Moderate | cat.tables, best-practice | failure | | +| [skip-link](https://dequeuniversity.com/rules/axe/4.11/skip-link?application=RuleDescription) | Ensure all skip links have a focusable target | Moderate | cat.keyboard, best-practice, RGAAv4, RGAA-12.7.1 | failure, needs review | | +| [tabindex](https://dequeuniversity.com/rules/axe/4.11/tabindex?application=RuleDescription) | Ensure tabindex attribute values are not greater than 0 | Serious | cat.keyboard, best-practice | failure | | +| [table-duplicate-name](https://dequeuniversity.com/rules/axe/4.11/table-duplicate-name?application=RuleDescription) | Ensure the <caption> element does not contain the same text as the summary attribute | Minor | cat.tables, best-practice, RGAAv4, RGAA-5.2.1 | failure, needs review | | ## WCAG 2.x level AAA rules @@ -154,9 +153,10 @@ Rules we are still testing and developing. They are disabled by default in axe-c Deprecated rules are disabled by default and will be removed in the next major release. -| Rule ID | Description | Impact | Tags | Issue Type | [ACT Rules](https://www.w3.org/WAI/standards-guidelines/act/rules/) | -| :------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------- | :------- | :--------------------------------------------------------------------------------------------------- | :------------------------- | :----------------------------------------------------------------------------------------------------- | -| [aria-roledescription](https://dequeuniversity.com/rules/axe/4.11/aria-roledescription?application=RuleDescription) | Ensure aria-roledescription is only used on elements with an implicit or explicit role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2, deprecated | failure, needs review | | -| [audio-caption](https://dequeuniversity.com/rules/axe/4.11/audio-caption?application=RuleDescription) | Ensure <audio> elements have captions | Critical | cat.time-and-media, wcag2a, wcag121, EN-301-549, EN-9.1.2.1, section508, section508.22.a, deprecated | needs review | [2eb176](https://act-rules.github.io/rules/2eb176), [afb423](https://act-rules.github.io/rules/afb423) | -| [duplicate-id-active](https://dequeuniversity.com/rules/axe/4.11/duplicate-id-active?application=RuleDescription) | Ensure every id attribute value of active elements is unique | Serious | cat.parsing, wcag2a-obsolete, wcag411, deprecated | failure | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | -| [duplicate-id](https://dequeuniversity.com/rules/axe/4.11/duplicate-id?application=RuleDescription) | Ensure every id attribute value is unique | Minor | cat.parsing, wcag2a-obsolete, wcag411, deprecated | failure | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | +| Rule ID | Description | Impact | Tags | Issue Type | [ACT Rules](https://www.w3.org/WAI/standards-guidelines/act/rules/) | +| :------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------- | :------- | :--------------------------------------------------------------------------------------------------- | :------------------------- | :----------------------------------------------------------------------------------------------------- | +| [aria-roledescription](https://dequeuniversity.com/rules/axe/4.11/aria-roledescription?application=RuleDescription) | Ensure aria-roledescription is only used on elements with an implicit or explicit role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2, deprecated | failure, needs review | | +| [audio-caption](https://dequeuniversity.com/rules/axe/4.11/audio-caption?application=RuleDescription) | Ensure <audio> elements have captions | Critical | cat.time-and-media, wcag2a, wcag121, EN-301-549, EN-9.1.2.1, section508, section508.22.a, deprecated | needs review | [2eb176](https://act-rules.github.io/rules/2eb176), [afb423](https://act-rules.github.io/rules/afb423) | +| [duplicate-id-active](https://dequeuniversity.com/rules/axe/4.11/duplicate-id-active?application=RuleDescription) | Ensure every id attribute value of active elements is unique | Serious | cat.parsing, wcag2a-obsolete, wcag411, deprecated | failure | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | +| [duplicate-id](https://dequeuniversity.com/rules/axe/4.11/duplicate-id?application=RuleDescription) | Ensure every id attribute value is unique | Minor | cat.parsing, wcag2a-obsolete, wcag411, deprecated | failure | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | +| [landmark-complementary-is-top-level](https://dequeuniversity.com/rules/axe/4.11/landmark-complementary-is-top-level?application=RuleDescription) | Ensure the complementary landmark or aside is at top level | Moderate | cat.semantics, best-practice, deprecated | failure | | diff --git a/lib/rules/landmark-complementary-is-top-level.json b/lib/rules/landmark-complementary-is-top-level.json index 638e392f1..60f7e319e 100644 --- a/lib/rules/landmark-complementary-is-top-level.json +++ b/lib/rules/landmark-complementary-is-top-level.json @@ -2,7 +2,8 @@ "id": "landmark-complementary-is-top-level", "impact": "moderate", "selector": "aside:not([role]), [role=complementary]", - "tags": ["cat.semantics", "best-practice"], + "tags": ["cat.semantics", "best-practice", "deprecated"], + "enabled": false, "metadata": { "description": "Ensure the complementary landmark or aside is at top level", "help": "Aside should not be contained in another landmark" From 0489e30aad3d80790d8fb9cf5b1807d7c3a2179f Mon Sep 17 00:00:00 2001 From: JustasM <59362982+JustasMonkev@users.noreply.github.com> Date: Tue, 27 Jan 2026 01:16:56 +0200 Subject: [PATCH 04/62] fix(aria-valid-attr-value): handle multiple aria-errormessage IDs (#4973) When aria-errormessage contains multiple space-separated IDs, the check now correctly verifies that all IDs are present in aria-describedby instead of failing to match the entire string against tokenized values. Closes: https://github.com/dequelabs/axe-core/issues/4957 --- lib/checks/aria/aria-errormessage-evaluate.js | 13 +++- lib/checks/aria/aria-errormessage.json | 1 + locales/_template.json | 1 + test/checks/aria/errormessage.js | 67 +++++++++++++++++-- .../aria-valid-attr-value.html | 34 ++++++++++ .../aria-valid-attr-value.json | 7 +- 6 files changed, 113 insertions(+), 10 deletions(-) diff --git a/lib/checks/aria/aria-errormessage-evaluate.js b/lib/checks/aria/aria-errormessage-evaluate.js index 2ef53f35e..977b2cce7 100644 --- a/lib/checks/aria/aria-errormessage-evaluate.js +++ b/lib/checks/aria/aria-errormessage-evaluate.js @@ -42,6 +42,12 @@ export default function ariaErrormessageEvaluate(node, options, virtualNode) { if (attr.trim() === '') { return standards.ariaAttrs['aria-errormessage'].allowEmpty; } + + const errormessageTokens = tokenList(attr); + if (errormessageTokens.length > 1) { + this.data({ messageKey: 'unsupported', values: errormessageTokens }); + return false; + } let idref; try { @@ -49,7 +55,7 @@ export default function ariaErrormessageEvaluate(node, options, virtualNode) { } catch { this.data({ messageKey: 'idrefs', - values: tokenList(attr) + values: errormessageTokens }); return undefined; } @@ -58,15 +64,16 @@ export default function ariaErrormessageEvaluate(node, options, virtualNode) { if (!isVisibleToScreenReaders(idref)) { this.data({ messageKey: 'hidden', - values: tokenList(attr) + values: errormessageTokens }); return false; } + const describedbyTokens = tokenList(virtualNode.attr('aria-describedby')); return ( getExplicitRole(idref) === 'alert' || idref.getAttribute('aria-live') === 'assertive' || idref.getAttribute('aria-live') === 'polite' || - tokenList(virtualNode.attr('aria-describedby')).indexOf(attr) > -1 + errormessageTokens.some(token => describedbyTokens.includes(token)) ); } diff --git a/lib/checks/aria/aria-errormessage.json b/lib/checks/aria/aria-errormessage.json index e238d7f65..bfc90e7f6 100644 --- a/lib/checks/aria/aria-errormessage.json +++ b/lib/checks/aria/aria-errormessage.json @@ -8,6 +8,7 @@ "fail": { "singular": "aria-errormessage value `${data.values}` must use a technique to announce the message (e.g., aria-live, aria-describedby, role=alert, etc.)", "plural": "aria-errormessage values `${data.values}` must use a technique to announce the message (e.g., aria-live, aria-describedby, role=alert, etc.)", + "unsupported": "Multiple IDs in aria-errormessage is not widely supported in assistive technologies", "hidden": "aria-errormessage value `${data.values}` cannot reference a hidden element" }, "incomplete": { diff --git a/locales/_template.json b/locales/_template.json index d1993f0d8..6bfc77bc6 100644 --- a/locales/_template.json +++ b/locales/_template.json @@ -462,6 +462,7 @@ "fail": { "singular": "aria-errormessage value `${data.values}` must use a technique to announce the message (e.g., aria-live, aria-describedby, role=alert, etc.)", "plural": "aria-errormessage values `${data.values}` must use a technique to announce the message (e.g., aria-live, aria-describedby, role=alert, etc.)", + "unsupported": "Multiple IDs in aria-errormessage is not widely supported in assistive technologies", "hidden": "aria-errormessage value `${data.values}` cannot reference a hidden element" }, "incomplete": { diff --git a/test/checks/aria/errormessage.js b/test/checks/aria/errormessage.js index fd95256b2..cef7ea31f 100644 --- a/test/checks/aria/errormessage.js +++ b/test/checks/aria/errormessage.js @@ -70,15 +70,72 @@ describe('aria-errormessage', function () { ); }); - it('sets an array of IDs in data', function () { + it('should return false if aria-errormessage has multiple ids (unsupported)', function () { + var vNode = queryFixture( + '' + + '
Error 1
' + + '
Error 2
' + ); + assert.isFalse( + axe.testUtils + .getCheckEvaluate('aria-errormessage') + .call(checkContext, null, null, vNode) + ); + assert.deepEqual(checkContext._data, { + messageKey: 'unsupported', + values: ['error1', 'error2'] + }); + }); + + it('should return false if aria-errormessage has multiple ids even when one is in aria-describedby', function () { + var vNode = queryFixture( + '' + + '
Error 1
' + + '
Error 2
' + ); + assert.isFalse( + axe.testUtils + .getCheckEvaluate('aria-errormessage') + .call(checkContext, null, null, vNode) + ); + assert.deepEqual(checkContext._data, { + messageKey: 'unsupported', + values: ['error1', 'error2'] + }); + }); + + it('should return false if aria-errormessage has multiple ids even when none are in aria-describedby', function () { + var vNode = queryFixture( + '' + + '
Other
' + + '
Error 1
' + + '
Error 2
' + ); + assert.isFalse( + axe.testUtils + .getCheckEvaluate('aria-errormessage') + .call(checkContext, null, null, vNode) + ); + assert.deepEqual(checkContext._data, { + messageKey: 'unsupported', + values: ['error1', 'error2'] + }); + }); + + it('sets an unsupported message when aria-errormessage contains multiple ids', function () { var vNode = queryFixture( '
' + '
' ); - axe.testUtils - .getCheckEvaluate('aria-errormessage') - .call(checkContext, null, null, vNode); - assert.deepEqual(checkContext._data, ['foo', 'bar', 'baz']); + assert.isFalse( + axe.testUtils + .getCheckEvaluate('aria-errormessage') + .call(checkContext, null, null, vNode) + ); + assert.deepEqual(checkContext._data, { + messageKey: 'unsupported', + values: ['foo', 'bar', 'baz'] + }); }); it('returns true when aria-errormessage is empty, if that is allowed', function () { diff --git a/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.html b/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.html index 1542a403b..cff70c3d2 100644 --- a/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.html +++ b/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.html @@ -90,6 +90,28 @@

Violations

/>
+ + +
+ +
Error message 1
+
Error message 2
+
+ +
+ +

Possible False Positives

@@ -349,6 +371,18 @@

Possible False Positives

aria-valuetext="" >
+
+ +
Error message 1
+
Error message 2
+
+
Hi
Hi2
diff --git a/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.json b/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.json index f0b46c6fc..089b2a48a 100644 --- a/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.json +++ b/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.json @@ -44,7 +44,9 @@ ["#violation43"], ["#violation44"], ["#violation45"], - ["#violation46"] + ["#violation46"], + ["#violation47"], + ["#violation48"] ], "passes": [ ["#pass1"], @@ -226,7 +228,8 @@ ["#pass188"], ["#pass189"], ["#pass190"], - ["#pass191"] + ["#pass191"], + ["#pass192"] ], "incomplete": [ ["#incomplete1"], From 4ec52112b67b1b44f82b3eade1825789ee8cb659 Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Thu, 29 Jan 2026 17:41:38 +0100 Subject: [PATCH 05/62] fix(scrollable-region-focusable): clarify the issue is in safari (#4995) Part of https://github.com/dequelabs/axe-core/issues/4830 Doesn't close the issue, but people keep reporting this as a false positive because we don't make it clear this is a Safari-only issue. Axe-core explicitly calling out a browser or screen reader is unusual, but I think the problem not doing this creates here is bigger than the overhead it would be for us to keep an eye on this and pull this out. If Safari ever addresses this the rule can be deprecated completely. See related Webkit bugs: - [WebKit#190870: Make scrollable element focusable](https://bugs.webkit.org/show_bug.cgi?id=190870) - [WebKit#277290: AX: Scrolling containers inoperable with keyboard](https://bugs.webkit.org/show_bug.cgi?id=277290) --- lib/rules/scrollable-region-focusable.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/scrollable-region-focusable.json b/lib/rules/scrollable-region-focusable.json index 44e02fe7e..ce3df6a9c 100644 --- a/lib/rules/scrollable-region-focusable.json +++ b/lib/rules/scrollable-region-focusable.json @@ -18,7 +18,7 @@ ], "actIds": ["0ssw9k"], "metadata": { - "description": "Ensure elements that have scrollable content are accessible by keyboard", + "description": "Ensure elements that have scrollable content are accessible by keyboard in Safari", "help": "Scrollable region must have keyboard access" }, "all": [], From c528814fcc4522873c1bb3603be610bafe344de5 Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Tue, 3 Feb 2026 09:37:57 -0700 Subject: [PATCH 06/62] chore: sync generated files (#5007) Since the [automatic action failed](https://github.com/dequelabs/axe-core/actions/runs/21486657500/job/61897684879) had to manually do it. --- doc/rule-descriptions.md | 2 +- locales/_template.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index 0b7997ee2..8f586e983 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -66,7 +66,7 @@ | [no-autoplay-audio](https://dequeuniversity.com/rules/axe/4.11/no-autoplay-audio?application=RuleDescription) | Ensure <video> or <audio> elements do not autoplay audio for more than 3 seconds without a control mechanism to stop or mute the audio | Moderate | cat.time-and-media, wcag2a, wcag142, TTv5, TT2.a, EN-301-549, EN-9.1.4.2, ACT, RGAAv4, RGAA-4.10.1 | needs review | [80f0bf](https://act-rules.github.io/rules/80f0bf) | | [object-alt](https://dequeuniversity.com/rules/axe/4.11/object-alt?application=RuleDescription) | Ensure <object> elements have alternative text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, EN-301-549, EN-9.1.1.1, RGAAv4, RGAA-1.1.6 | failure, needs review | [8fc3b6](https://act-rules.github.io/rules/8fc3b6) | | [role-img-alt](https://dequeuniversity.com/rules/axe/4.11/role-img-alt?application=RuleDescription) | Ensure [role="img"] elements have alternative text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, TTv5, TT7.a, EN-301-549, EN-9.1.1.1, ACT, RGAAv4, RGAA-1.1.1 | failure, needs review | [23a2a8](https://act-rules.github.io/rules/23a2a8) | -| [scrollable-region-focusable](https://dequeuniversity.com/rules/axe/4.11/scrollable-region-focusable?application=RuleDescription) | Ensure elements that have scrollable content are accessible by keyboard | Serious | cat.keyboard, wcag2a, wcag211, wcag213, TTv5, TT4.a, EN-301-549, EN-9.2.1.1, EN-9.2.1.3, RGAAv4, RGAA-7.3.2 | failure | [0ssw9k](https://act-rules.github.io/rules/0ssw9k) | +| [scrollable-region-focusable](https://dequeuniversity.com/rules/axe/4.11/scrollable-region-focusable?application=RuleDescription) | Ensure elements that have scrollable content are accessible by keyboard in Safari | Serious | cat.keyboard, wcag2a, wcag211, wcag213, TTv5, TT4.a, EN-301-549, EN-9.2.1.1, EN-9.2.1.3, RGAAv4, RGAA-7.3.2 | failure | [0ssw9k](https://act-rules.github.io/rules/0ssw9k) | | [select-name](https://dequeuniversity.com/rules/axe/4.11/select-name?application=RuleDescription) | Ensure select element has an accessible name | Critical | cat.forms, wcag2a, wcag412, section508, section508.22.n, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT, RGAAv4, RGAA-11.1.1 | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | | [server-side-image-map](https://dequeuniversity.com/rules/axe/4.11/server-side-image-map?application=RuleDescription) | Ensure that server-side image maps are not used | Minor | cat.text-alternatives, wcag2a, wcag211, section508, section508.22.f, TTv5, TT4.a, EN-301-549, EN-9.2.1.1, RGAAv4, RGAA-1.1.4 | needs review | | | [summary-name](https://dequeuniversity.com/rules/axe/4.11/summary-name?application=RuleDescription) | Ensure summary elements have discernible text | Serious | cat.name-role-value, wcag2a, wcag412, section508, section508.22.a, TTv5, TT6.a, EN-301-549, EN-9.4.1.2 | failure, needs review | | diff --git a/locales/_template.json b/locales/_template.json index 6bfc77bc6..70203bd75 100644 --- a/locales/_template.json +++ b/locales/_template.json @@ -358,7 +358,7 @@ "help": "scope attribute should be used correctly" }, "scrollable-region-focusable": { - "description": "Ensure elements that have scrollable content are accessible by keyboard", + "description": "Ensure elements that have scrollable content are accessible by keyboard in Safari", "help": "Scrollable region must have keyboard access" }, "select-name": { From 835e9302324392828691ea720f3ee3f652390831 Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Tue, 3 Feb 2026 09:38:12 -0700 Subject: [PATCH 07/62] ci: fix update-generated-files workflow (#5006) It would seem that the `GITHUB_OUTPUT` syntax [does not allow line breaks](https://github.com/orgs/community/discussions/106666). Since the output to `git status --porcelain` produces a line with 2 changes ``` M doc/rule-descriptions.md M locales/_template.json ``` removing the line breaks from the variable should fix the problem. --- .github/workflows/update-generated-files.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-generated-files.yaml b/.github/workflows/update-generated-files.yaml index 1968e89c5..6ac667002 100644 --- a/.github/workflows/update-generated-files.yaml +++ b/.github/workflows/update-generated-files.yaml @@ -29,7 +29,7 @@ jobs: - name: Check for changes id: changes run: | - changes=$(git status --porcelain) + changes=$(git status --porcelain | tr -d '\n') # see https://unix.stackexchange.com/a/509498 echo $changes | grep . && echo "Changes detected" || echo "No changes" echo "changes=$changes" >> "$GITHUB_OUTPUT" From 5761cdaf3c30760ce202a165b01b8b753ff8c086 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 09:56:21 -0700 Subject: [PATCH 08/62] chore: bump globals from 16.5.0 to 17.1.0 (#5005) Bumps [globals](https://github.com/sindresorhus/globals) from 16.5.0 to 17.1.0.
Release notes

Sourced from globals's releases.

v17.1.0

  • Add webpack and rspack globals (#333) 65cae73

https://github.com/sindresorhus/globals/compare/v17.0.0...v17.1.0

v17.0.0

Breaking

  • Split audioWorklet environment from browser (#320) 7bc293e

Improvements

  • Update globals (#329) ebe1063
  • Get all browser globals from both chrome and firefox (#321) 59ceff8
  • Add bunBuiltin environment (#324) 1bc6e3b
  • Add denoBuiltin environment (#324) 1bc6e3b
  • Add paintWorklet environment (#323) 4b78f56
  • Add sharedWorker environment (#322) 4a02a85

https://github.com/sindresorhus/globals/compare/v16.5.0...v17.0.0

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=globals&package-manager=npm_and_yarn&previous-version=16.5.0&new-version=17.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index ae7ad8e7b..840d7830d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "eslint-plugin-mocha-no-only": "^1.2.0", "execa": "5.x", "glob": "^10.3.10", - "globals": "^16.0.0", + "globals": "^17.1.0", "grunt": "^1.5.3", "grunt-babel": "^8.0.0", "grunt-bytesize": "^0.2.0", @@ -6376,9 +6376,9 @@ } }, "node_modules/globals": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.1.0.tgz", + "integrity": "sha512-8HoIcWI5fCvG5NADj4bDav+er9B9JMj2vyL2pI8D0eismKyUvPLTSs+Ln3wqhwcp306i73iyVnEKx3F6T47TGw==", "dev": true, "engines": { "node": ">=18" @@ -17922,9 +17922,9 @@ } }, "globals": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.1.0.tgz", + "integrity": "sha512-8HoIcWI5fCvG5NADj4bDav+er9B9JMj2vyL2pI8D0eismKyUvPLTSs+Ln3wqhwcp306i73iyVnEKx3F6T47TGw==", "dev": true }, "globule": { diff --git a/package.json b/package.json index 1fdfb77ce..afb51ce7a 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "eslint-plugin-mocha-no-only": "^1.2.0", "execa": "5.x", "glob": "^10.3.10", - "globals": "^16.0.0", + "globals": "^17.1.0", "grunt": "^1.5.3", "grunt-babel": "^8.0.0", "grunt-bytesize": "^0.2.0", From c994928ff3ba52e4825e26b8fb4ae33a43445605 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 11:26:25 -0700 Subject: [PATCH 09/62] chore: bump jquery from 3.7.1 to 4.0.0 (#5004) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [jquery](https://github.com/jquery/jquery) from 3.7.1 to 4.0.0.
Release notes

Sourced from jquery's releases.

Release 4.0.0

Changelog

https://blog.jquery.com/2026/01/17/jquery-4-0-0/

Ajax

  • Don't treat array data as binary (992a1911)
  • Allow processData: true even for binary data (ce264e07)
  • Support binary data (including FormData) (a7ed9a7b)
  • Support headers for script transport even when cross-domain (#5142, 6d136443)
  • Support null as success functions in jQuery.get (#4989, 74978b7e)
  • Don't auto-execute scripts unless dataType provided (#4822, 025da4dd)
  • Make responseJSON work for erroneous same-domain JSONP requests (68b4ec59)
  • Execute JSONP error script responses (#4771, a1e619b0)
  • Avoid CSP errors in the script transport for async requests (#3969, 07a8e4a1)
  • Drop the json to jsonp auto-promotion logic (#1799, #3376, e7b3bc48)
  • Overwrite s.contentType with content-type header value, if any (#4119, 7fb90a6b)
  • Deprecate AJAX event aliases, inline event/alias into deprecated (23d53928)
  • Do not execute scripts for unsuccessful HTTP responses (#4250, 50871a5a)
  • Simplify jQuery.ajaxSettings.xhr (#1967, abdc89ac)

Attributes

  • Make .attr( name, false ) remove for all non-ARIA attrs (#5388, 063831b6)
  • Shave off a couple of bytes (b40a4807)
  • Don't stringify attributes in the setter (#4948, 4250b628)
  • Drop the toggleClass(boolean|undefined) signature (#3388, a4421101)
  • Refactor val(): don't strip carriage return, isolate IE workarounds (ff281991)
  • Don't set the type attr hook at all outside of IE (9e66fe9a)

CSS

  • Fix dimensions of table <col> elements (#5628, eca2a564)
  • Drop the cache in finalPropName (640d5825)
  • Tests: Fix tests & support tests under CSS Zoom (#5489, 071f6dba)
  • Fix reliableTrDimensions support test for initially hidden iframes (b1e66a5f)
  • Selector: Align with 3.x, remove the outer selector.js wrapper (53cf7244)
  • Make the reliableTrDimensions support test work with Bootstrap CSS (#5270, 65b85031)
  • Make offsetHeight( true ), etc. include negative margins (#3982, bce13b72)
  • Return undefined for whitespace-only CSS variable values (#5120) (7eb00196)
  • Don’t trim whitespace of undefined custom property (#5105, ed306c02)
  • Skip falsy values in addClass( array ), compress code (#4998, a338b407)
  • Justify use of rtrim on CSS property values (655c0ed5)
  • Trim whitespace surrounding CSS Custom Properties values (#4926, efadfe99)
  • Include show, hide & toggle methods in the jQuery slim build (297d18dd)
  • Remove the opacity CSS hook (865469f5)
  • Workaround buggy getComputedStyle on table rows in IE/Edge (#4490, 26415e08)
  • Don't automatically add "px" to properties with a few exceptions (#2795, 00a9c2e5)

... (truncated)

Commits
  • 4f2fae0 Release: 4.0.0
  • c838cfb Release: remove dist files from main branch
  • 9752519 Release: 4.0.0-rc.2
  • c128d5d Release: Update AUTHORS.txt
  • 5fe9c29 Build: De-dupe three authors via mailmap
  • afdd032 Build: Post beta browser tests errors to jquery/dev on Matrix
  • 546a1eb Build: Bump the github-actions group with 4 updates
  • ec738b3 Build: Fix Chrome beta tests
  • c28c26a Build: Add periodic tests on beta versions of browsers
  • f513413 Build: Bump the github-actions group with 2 updates
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=jquery&package-manager=npm_and_yarn&previous-version=3.7.1&new-version=4.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 840d7830d..e8f8aa471 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "http-server": "^14.1.1", "husky": "^9.0.7", "inquirer": "^8.2.5", - "jquery": "^3.6.3", + "jquery": "^4.0.0", "jsdoc": "^4.0.2", "jsdom": "^27.0.0", "karma": "^6.4.1", @@ -7908,9 +7908,9 @@ } }, "node_modules/jquery": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", - "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-4.0.0.tgz", + "integrity": "sha512-TXCHVR3Lb6TZdtw1l3RTLf8RBWVGexdxL6AC8/e0xZKEpBflBsjh9/8LXw+dkNFuOyW9B7iB3O1sP7hS0Kiacg==", "dev": true }, "node_modules/js-tokens": { @@ -19016,9 +19016,9 @@ } }, "jquery": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", - "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-4.0.0.tgz", + "integrity": "sha512-TXCHVR3Lb6TZdtw1l3RTLf8RBWVGexdxL6AC8/e0xZKEpBflBsjh9/8LXw+dkNFuOyW9B7iB3O1sP7hS0Kiacg==", "dev": true }, "js-tokens": { diff --git a/package.json b/package.json index afb51ce7a..c5b7c3fb0 100644 --- a/package.json +++ b/package.json @@ -156,7 +156,7 @@ "http-server": "^14.1.1", "husky": "^9.0.7", "inquirer": "^8.2.5", - "jquery": "^3.6.3", + "jquery": "^4.0.0", "jsdoc": "^4.0.2", "jsdom": "^27.0.0", "karma": "^6.4.1", From 838707a8f224907042221bbf6fb28d6ad59d7cb0 Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Thu, 5 Feb 2026 07:58:31 -0700 Subject: [PATCH 10/62] fix(scrollable-region-focusable): do not fail scroll areas when all content is visible without scrolling (#4993) Scrollable-region-focusable-matches now tests the text rects or bounding rect of all content to see if is outside the bounding rect of the scroll container. Only then does it pass the matcher. This changed made many of the integration test cases inapplicable so I moved them to the bottom of the file to be grouped with the other inapplicable results. For the unit tests I had to pad some of the tests with more content in order for the visible content to require scrolling. Closes: https://github.com/dequelabs/axe-core/issues/4535 --- .../scrollable-region-focusable-matches.js | 40 ++++++-- .../scrollable-region-focusable.html | 95 +++++++++++++------ .../scrollable-region-focusable.json | 11 +-- .../scrollable-region-focusable-matches.js | 46 ++++++++- 4 files changed, 144 insertions(+), 48 deletions(-) diff --git a/lib/rules/scrollable-region-focusable-matches.js b/lib/rules/scrollable-region-focusable-matches.js index 34c4b08d7..9f1cad3b6 100644 --- a/lib/rules/scrollable-region-focusable-matches.js +++ b/lib/rules/scrollable-region-focusable-matches.js @@ -1,4 +1,6 @@ -import hasContentVirtual from '../commons/dom/has-content-virtual'; +import hasContentVirtual, { + hasChildTextNodes +} from '../commons/dom/has-content-virtual'; import isComboboxPopup from '../commons/aria/is-combobox-popup'; import { querySelectorAll, getScroll } from '../core/utils'; @@ -9,13 +11,39 @@ export default function scrollableRegionFocusableMatches(node, virtualNode) { // It's not a combobox popup, which commonly has keyboard focus added isComboboxPopup(virtualNode) === false && // And there's something actually worth scrolling to - isNoneEmptyElement(virtualNode) + isNonEmptyElementOutsideViewableRect(virtualNode) ); } -function isNoneEmptyElement(vNode) { - return querySelectorAll(vNode, '*').some(elm => +function isNonEmptyElementOutsideViewableRect(vNode) { + const boundingRect = vNode.boundingClientRect; + + return querySelectorAll(vNode, '*').some(elm => { // (elm, noRecursion, ignoreAria) - hasContentVirtual(elm, true, true) - ); + if (!hasContentVirtual(elm, true, true)) { + return false; + } + + let rects = []; + if (hasChildTextNodes(elm)) { + rects.push(...getContentRects(elm)); + } else { + rects = [elm.boundingClientRect]; + } + + return rects.some(rect => { + return ( + rect.left < boundingRect.left || + rect.right > boundingRect.right || + rect.top < boundingRect.top || + rect.bottom > boundingRect.bottom + ); + }); + }); +} + +function getContentRects(vNode) { + const range = document.createRange(); + range.selectNodeContents(vNode.actualNode); + return Array.from(range.getClientRects()); } diff --git a/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.html b/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.html index 750785c2b..24dfc3c0b 100644 --- a/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.html +++ b/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.html @@ -3,39 +3,13 @@ -
-
-

Content

-
-
- -
+

-
-
-

Content

-
-
- -
-
-
-
-

Content

-
-
-
-
-
@@ -47,10 +21,17 @@
-
-
-

Content

-
+
+

Contents

+

+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium + doloremque laudantium. +

+
+ +
+

Contents

+
@@ -144,3 +125,55 @@ test test + +
+
+

Content

+
+
+ +
+
+

Content

+
+
+ +
+
+
+
+

Content

+
+
+
+
+ +
+
+

Content

+
+
+ +
+

Contents

+
+ +
+ +
diff --git a/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.json b/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.json index 50220b0bd..e18c85ad3 100644 --- a/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.json +++ b/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.json @@ -1,13 +1,6 @@ { "description": "scrollable-region-focusable tests", "rule": "scrollable-region-focusable", - "violations": [["#fail1"], ["#fail2"], ["#fail3"]], - "passes": [ - ["#pass1"], - ["#pass2"], - ["#pass3"], - ["#pass4"], - ["#pass5"], - ["#pass6"] - ] + "violations": [["#fail1"], ["#fail2"], ["#fail3"], ["#fail4"]], + "passes": [["#pass1"], ["#pass2"]] } diff --git a/test/rule-matches/scrollable-region-focusable-matches.js b/test/rule-matches/scrollable-region-focusable-matches.js index 01dfe1771..7900610c4 100644 --- a/test/rule-matches/scrollable-region-focusable-matches.js +++ b/test/rule-matches/scrollable-region-focusable-matches.js @@ -56,7 +56,7 @@ describe('scrollable-region-focusable-matches', function () { const target = queryFixture( '
' + '
' + - '

Content

' + + '

Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium

' + '
' + '
' ); @@ -138,7 +138,49 @@ describe('scrollable-region-focusable-matches', function () { it('returns true for combobox with non-valid role', function () { const target = queryFixture( - '
  • Option
' + '
  • Option
  • Option
  • Option
  • Option
' + ); + const actual = rule.matches(target.actualNode, target); + assert.isTrue(actual); + }); + + it('returns false when all text is visible without scrolling', () => { + const target = queryFixture( + `
+

Contents

+
` + ); + const actual = rule.matches(target.actualNode, target); + assert.isFalse(actual); + }); + + it('returns false when all visible content is visible without scrolling', () => { + const target = queryFixture( + `
+ +
` + ); + const actual = rule.matches(target.actualNode, target); + assert.isFalse(actual); + }); + + it('returns true when any visible text is not visible without scrolling', () => { + const target = queryFixture( + `
+

Contents

+

Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.

+
` + ); + const actual = rule.matches(target.actualNode, target); + assert.isTrue(actual); + }); + + it('returns true when any visible content is not visible without scrolling', () => { + const target = queryFixture( + `
+

Contents

+ +
` ); const actual = rule.matches(target.actualNode, target); assert.isTrue(actual); From b8e6a5943f3d7613e770f36dd15fdb27621ca18c Mon Sep 17 00:00:00 2001 From: Dima Date: Thu, 5 Feb 2026 10:59:58 -0500 Subject: [PATCH 11/62] feat(core): expose normalizeRunOptions (#4998) Move normalizeOptions from axe._audit to axe.utils.normalizeRunOptions Related to: https://github.com/dequelabs/axe-pro-ml-service/issues/1296 --- axe.d.ts | 4 + lib/core/base/audit.js | 84 +------ lib/core/public/finish-run.js | 5 +- lib/core/utils/index.js | 1 + lib/core/utils/normalize-run-options.js | 79 +++++++ test/core/base/audit.js | 235 ++------------------ test/core/utils/normalize-run-options.js | 268 +++++++++++++++++++++++ 7 files changed, 372 insertions(+), 304 deletions(-) create mode 100644 lib/core/utils/normalize-run-options.js create mode 100644 test/core/utils/normalize-run-options.js diff --git a/axe.d.ts b/axe.d.ts index 786b30a23..c679dd554 100644 --- a/axe.d.ts +++ b/axe.d.ts @@ -147,6 +147,9 @@ declare namespace axe { performanceTimer?: boolean; pingWaitTime?: number; } + interface NormalizedRunOptions extends RunOptions { + runOnly?: RunOnly; + } interface PreloadOptions { assets: string[]; timeout?: number; @@ -497,6 +500,7 @@ declare namespace axe { offset?: number ) => string | Uint8Array | Array; nodeSerializer: NodeSerializer; + normalizeRunOptions: (options?: RunOptions) => NormalizedRunOptions; } interface Aria { diff --git a/lib/core/base/audit.js b/lib/core/base/audit.js index 3353545af..c44593d1d 100644 --- a/lib/core/base/audit.js +++ b/lib/core/base/audit.js @@ -10,7 +10,8 @@ import { findBy, ruleShouldRun, performanceTimer, - serializeError + serializeError, + normalizeRunOptions } from '../utils'; import { doT } from '../imports'; import constants from '../constants'; @@ -271,7 +272,7 @@ export default class Audit { * @param {Function} reject Callback function to fire when audit experiences an error */ run(context, options, resolve, reject) { - this.normalizeOptions(options); + normalizeRunOptions(options); DqElement.setRunOptions(options); // TODO: es-modules_selectCache @@ -397,86 +398,7 @@ export default class Audit { getRule(ruleId) { return this.rules.find(rule => rule.id === ruleId); } - /** - * Ensure all rules that are expected to run exist - * @throws {Error} If any tag or rule specified in options is unknown - * @param {Object} options Options object - * @return {Object} Validated options object - */ - normalizeOptions(options) { - const audit = this; - const tags = []; - const ruleIds = []; - audit.rules.forEach(rule => { - ruleIds.push(rule.id); - rule.tags.forEach(tag => { - if (!tags.includes(tag)) { - tags.push(tag); - } - }); - }); - // Validate runOnly - if (['object', 'string'].includes(typeof options.runOnly)) { - if (typeof options.runOnly === 'string') { - options.runOnly = [options.runOnly]; - } - if (Array.isArray(options.runOnly)) { - const hasTag = options.runOnly.find(value => tags.includes(value)); - const hasRule = options.runOnly.find(value => ruleIds.includes(value)); - if (hasTag && hasRule) { - throw new Error('runOnly cannot be both rules and tags'); - } - if (hasRule) { - options.runOnly = { - type: 'rule', - values: options.runOnly - }; - } else { - options.runOnly = { - type: 'tag', - values: options.runOnly - }; - } - } - const only = options.runOnly; - if (only.value && !only.values) { - only.values = only.value; - delete only.value; - } - if (!Array.isArray(only.values) || only.values.length === 0) { - throw new Error('runOnly.values must be a non-empty array'); - } - // Check if every value in options.runOnly is a known rule ID - if (['rule', 'rules'].includes(only.type)) { - only.type = 'rule'; - only.values.forEach(ruleId => { - if (!ruleIds.includes(ruleId)) { - throw new Error('unknown rule `' + ruleId + '` in options.runOnly'); - } - }); - // Validate 'tags' (e.g. anything not 'rule') - } else if (['tag', 'tags', undefined].includes(only.type)) { - only.type = 'tag'; - const unmatchedTags = only.values.filter( - tag => !tags.includes(tag) && !/wcag2[1-3]a{1,3}/.test(tag) - ); - if (unmatchedTags.length !== 0) { - axe.log('Could not find tags `' + unmatchedTags.join('`, `') + '`'); - } - } else { - throw new Error(`Unknown runOnly type '${only.type}'`); - } - } - if (typeof options.rules === 'object') { - Object.keys(options.rules).forEach(ruleId => { - if (!ruleIds.includes(ruleId)) { - throw new Error('unknown rule `' + ruleId + '` in options.rules'); - } - }); - } - return options; - } /* * Updates the default options and then applies them * @param {Mixed} options Options object diff --git a/lib/core/public/finish-run.js b/lib/core/public/finish-run.js index 31e6417c2..83f3ca8f1 100644 --- a/lib/core/public/finish-run.js +++ b/lib/core/public/finish-run.js @@ -4,7 +4,8 @@ import { publishMetaData, finalizeRuleResult, nodeSerializer, - clone + clone, + normalizeRunOptions } from '../utils'; export default function finishRun(partialResults, options = {}) { @@ -12,7 +13,7 @@ export default function finishRun(partialResults, options = {}) { const { environmentData } = partialResults.find(r => r.environmentData) || {}; // normalize the runOnly option for the output of reporters toolOptions - axe._audit.normalizeOptions(options); + normalizeRunOptions(options); options.reporter = options.reporter ?? axe._audit?.reporter ?? 'v1'; setFrameSpec(partialResults); diff --git a/lib/core/utils/index.js b/lib/core/utils/index.js index 01ce2fa08..691b25a17 100644 --- a/lib/core/utils/index.js +++ b/lib/core/utils/index.js @@ -98,3 +98,4 @@ export { default as uniqueArray } from './unique-array'; export { default as uuid } from './uuid'; export { default as validInputTypes } from './valid-input-type'; export { default as isValidLang, validLangs } from './valid-langs'; +export { default as normalizeRunOptions } from './normalize-run-options'; diff --git a/lib/core/utils/normalize-run-options.js b/lib/core/utils/normalize-run-options.js new file mode 100644 index 000000000..1c65d518c --- /dev/null +++ b/lib/core/utils/normalize-run-options.js @@ -0,0 +1,79 @@ +/** + * Ensure all rules that are expected to run exist + * @throws {Error} If any tag or rule specified in options is unknown + * @param {Object} options Options object + * @return {Object} Validated options object + */ +export default function normalizeRunOptions(options = {}) { + const tags = []; + const ruleIds = []; + axe._audit.rules.forEach(rule => { + ruleIds.push(rule.id); + rule.tags.forEach(tag => { + if (!tags.includes(tag)) { + tags.push(tag); + } + }); + }); + // Validate runOnly + if (['object', 'string'].includes(typeof options.runOnly)) { + if (typeof options.runOnly === 'string') { + options.runOnly = [options.runOnly]; + } + if (Array.isArray(options.runOnly)) { + const hasTag = options.runOnly.find(value => tags.includes(value)); + const hasRule = options.runOnly.find(value => ruleIds.includes(value)); + if (hasTag && hasRule) { + throw new Error('runOnly cannot be both rules and tags'); + } + if (hasRule) { + options.runOnly = { + type: 'rule', + values: options.runOnly + }; + } else { + options.runOnly = { + type: 'tag', + values: options.runOnly + }; + } + } + const only = options.runOnly; + if (only.value && !only.values) { + only.values = only.value; + delete only.value; + } + if (!Array.isArray(only.values) || only.values.length === 0) { + throw new Error('runOnly.values must be a non-empty array'); + } + // Check if every value in options.runOnly is a known rule ID + if (['rule', 'rules'].includes(only.type)) { + only.type = 'rule'; + only.values.forEach(ruleId => { + if (!ruleIds.includes(ruleId)) { + throw new Error('unknown rule `' + ruleId + '` in options.runOnly'); + } + }); + // Validate 'tags' (e.g. anything not 'rule') + } else if (['tag', 'tags', undefined].includes(only.type)) { + only.type = 'tag'; + + const unmatchedTags = only.values.filter( + tag => !tags.includes(tag) && !/wcag2[1-3]a{1,3}/.test(tag) + ); + if (unmatchedTags.length !== 0) { + axe.log('Could not find tags `' + unmatchedTags.join('`, `') + '`'); + } + } else { + throw new Error(`Unknown runOnly type '${only.type}'`); + } + } + if (typeof options.rules === 'object') { + Object.keys(options.rules).forEach(ruleId => { + if (!ruleIds.includes(ruleId)) { + throw new Error('unknown rule `' + ruleId + '` in options.rules'); + } + }); + } + return options; +} diff --git a/test/core/base/audit.js b/test/core/base/audit.js index 12c364410..2bcc2d0b8 100644 --- a/test/core/base/audit.js +++ b/test/core/base/audit.js @@ -86,6 +86,7 @@ describe('Audit', () => { const fixture = document.getElementById('fixture'); let origAuditRun; + let origUtils; beforeEach(() => { audit = new Audit(); mockRules.forEach(function (r) { @@ -95,11 +96,14 @@ describe('Audit', () => { audit.addCheck(c); }); origAuditRun = audit.run; + origUtils = axe.utils; + axe._audit = audit; }); afterEach(() => { axe.teardown(); audit.run = origAuditRun; + axe.utils = origUtils; }); it('should be a function', () => { @@ -1155,6 +1159,7 @@ describe('Audit', () => { }); it('should not halt if errors occur', done => { + axe._audit = audit; audit.addRule({ id: 'throw1', selector: '*', @@ -1185,25 +1190,20 @@ describe('Audit', () => { ); }); - it('should run audit.normalizeOptions to ensure valid input', () => { + it('should run axe.utils.normalizeRunOptions to ensure valid input', () => { fixture.innerHTML = '' + '
bananas
' + '' + 'FAIL ME'; - let checked = 'options not validated'; - - audit.normalizeOptions = () => { - checked = 'options validated'; - }; - - audit.run( - { include: [axe.utils.getFlattenedTree(fixture)[0]] }, - {}, - noop, - isNotCalled - ); - assert.equal(checked, 'options validated'); + assert.throws(() => { + audit.run( + { include: [axe.utils.getFlattenedTree(fixture)[0]] }, + { runOnly: { type: 'rule', values: ['foo'] } }, + noop, + isNotCalled + ); + }, 'unknown rule `foo` in options.runOnly'); }); it('propagates DqElement options', async () => { @@ -1389,211 +1389,4 @@ describe('Audit', () => { } }); }); - - describe('Audit#normalizeOptions', () => { - let axeLog; - beforeEach(() => { - axeLog = axe.log; - }); - afterEach(() => { - axe.log = axeLog; - }); - - it('returns the options object when it is valid', () => { - const opt = { - runOnly: { - type: 'rule', - values: ['positive1', 'positive2'] - }, - rules: { - negative1: { enabled: false } - } - }; - assert(audit.normalizeOptions(opt), opt); - }); - - it('allows `value` as alternative to `values`', () => { - const opt = { - runOnly: { - type: 'rule', - value: ['positive1', 'positive2'] - } - }; - const out = audit.normalizeOptions(opt); - assert.deepEqual(out.runOnly.values, ['positive1', 'positive2']); - assert.isUndefined(out.runOnly.value); - }); - - it('allows type: rules as an alternative to type: rule', () => { - const opt = { - runOnly: { - type: 'rules', - values: ['positive1', 'positive2'] - } - }; - assert(audit.normalizeOptions(opt).runOnly.type, 'rule'); - }); - - it('allows type: tags as an alternative to type: tag', () => { - const opt = { - runOnly: { - type: 'tags', - values: ['positive'] - } - }; - assert(audit.normalizeOptions(opt).runOnly.type, 'tag'); - }); - - it('allows type: undefined as an alternative to type: tag', () => { - const opt = { - runOnly: { - values: ['positive'] - } - }; - assert(audit.normalizeOptions(opt).runOnly.type, 'tag'); - }); - - it('allows runOnly as an array as an alternative to type: tag', () => { - const opt = { runOnly: ['positive', 'negative'] }; - const out = audit.normalizeOptions(opt); - assert(out.runOnly.type, 'tag'); - assert.deepEqual(out.runOnly.values, ['positive', 'negative']); - }); - - it('allows runOnly as an array as an alternative to type: rule', () => { - const opt = { runOnly: ['positive1', 'negative1'] }; - const out = audit.normalizeOptions(opt); - assert(out.runOnly.type, 'rule'); - assert.deepEqual(out.runOnly.values, ['positive1', 'negative1']); - }); - - it('allows runOnly as a string as an alternative to an array', () => { - const opt = { runOnly: 'positive1' }; - const out = audit.normalizeOptions(opt); - assert(out.runOnly.type, 'rule'); - assert.deepEqual(out.runOnly.values, ['positive1']); - }); - - it('throws an error if runOnly contains both rules and tags', () => { - assert.throws(() => { - audit.normalizeOptions({ - runOnly: ['positive', 'negative1'] - }); - }); - }); - - it('defaults runOnly to type: tag', () => { - const opt = { runOnly: ['fakeTag'] }; - const out = audit.normalizeOptions(opt); - assert(out.runOnly.type, 'tag'); - assert.deepEqual(out.runOnly.values, ['fakeTag']); - }); - - it('throws an error runOnly.values not an array', () => { - assert.throws(() => { - audit.normalizeOptions({ - runOnly: { - type: 'rule', - values: { badProp: 'badValue' } - } - }); - }); - }); - - it('throws an error runOnly.values an empty', () => { - assert.throws(() => { - audit.normalizeOptions({ - runOnly: { - type: 'rule', - values: [] - } - }); - }); - }); - - it('throws an error runOnly.type is unknown', () => { - assert.throws(() => { - audit.normalizeOptions({ - runOnly: { - type: 'something-else', - values: ['wcag2aa'] - } - }); - }); - }); - - it('throws an error when option.runOnly has an unknown rule', () => { - assert.throws(() => { - audit.normalizeOptions({ - runOnly: { - type: 'rule', - values: ['frakeRule'] - } - }); - }); - }); - - it("doesn't throw an error when option.runOnly has an unknown tag", () => { - assert.doesNotThrow(() => { - audit.normalizeOptions({ - runOnly: { - type: 'tags', - values: ['fakeTag'] - } - }); - }); - }); - - it('throws an error when option.rules has an unknown rule', () => { - assert.throws(() => { - audit.normalizeOptions({ - rules: { - fakeRule: { enabled: false } - } - }); - }); - }); - - it('logs an issue when a tag is unknown', () => { - let message = ''; - axe.log = function (m) { - message = m; - }; - audit.normalizeOptions({ - runOnly: { - type: 'tags', - values: ['unknwon-tag'] - } - }); - assert.include(message, 'Could not find tags'); - }); - - it('logs no issues for unknown WCAG level tags', () => { - let message = ''; - axe.log = function (m) { - message = m; - }; - audit.normalizeOptions({ - runOnly: { - type: 'tags', - values: ['wcag23aaa'] - } - }); - assert.isEmpty(message); - }); - - it('logs an issue when a tag is unknown, together with a wcag level tag', () => { - let message = ''; - axe.log = function (m) { - message = m; - }; - audit.normalizeOptions({ - runOnly: { - type: 'tags', - values: ['wcag23aaa', 'unknwon-tag'] - } - }); - assert.include(message, 'Could not find tags'); - }); - }); }); diff --git a/test/core/utils/normalize-run-options.js b/test/core/utils/normalize-run-options.js new file mode 100644 index 000000000..6409b553f --- /dev/null +++ b/test/core/utils/normalize-run-options.js @@ -0,0 +1,268 @@ +describe('axe.utils.normalizeRunOptions', () => { + const mockChecks = [ + { + id: 'positive1-check1', + evaluate: () => true + }, + { + id: 'positive2-check1', + evaluate: () => true + }, + { + id: 'negative1-check1', + evaluate: () => true + }, + { + id: 'positive3-check1', + evaluate: () => true + } + ]; + + const mockRules = [ + { + id: 'positive1', + selector: 'input', + tags: ['positive'], + any: [ + { + id: 'positive1-check1' + } + ] + }, + { + id: 'positive2', + selector: '#monkeys', + tags: ['positive'], + any: ['positive2-check1'] + }, + { + id: 'negative1', + selector: 'div', + tags: ['negative'], + none: ['negative1-check1'] + }, + { + id: 'positive3', + selector: 'blink', + tags: ['positive'], + any: ['positive3-check1'] + } + ]; + const Audit = axe._thisWillBeDeletedDoNotUse.base.Audit; + let axeLog; + let axeAudit; + let audit; + beforeEach(() => { + axeLog = axe.log; + axeAudit = axe._audit; + audit = new Audit(); + axe._audit = audit; + mockRules.forEach(function (r) { + audit.addRule(r); + }); + mockChecks.forEach(function (c) { + audit.addCheck(c); + }); + }); + afterEach(() => { + axe.log = axeLog; + axe._audit = axeAudit; + }); + + it('returns the options object when it is valid', () => { + const opt = { + runOnly: { + type: 'rule', + values: ['positive1', 'positive2'] + }, + rules: { + negative1: { enabled: false } + } + }; + assert.equal(axe.utils.normalizeRunOptions(opt), opt); + }); + + it('allows `value` as alternative to `values`', () => { + const opt = { + runOnly: { + type: 'rule', + value: ['positive1', 'positive2'] + } + }; + const out = axe.utils.normalizeRunOptions(opt); + assert.deepEqual(out.runOnly.values, ['positive1', 'positive2']); + assert.isUndefined(out.runOnly.value); + }); + + it('allows type: rules as an alternative to type: rule', () => { + const opt = { + runOnly: { + type: 'rules', + values: ['positive1', 'positive2'] + } + }; + assert.equal(axe.utils.normalizeRunOptions(opt).runOnly.type, 'rule'); + }); + + it('allows type: tags as an alternative to type: tag', () => { + const opt = { + runOnly: { + type: 'tags', + values: ['positive'] + } + }; + assert.equal(axe.utils.normalizeRunOptions(opt).runOnly.type, 'tag'); + }); + + it('allows type: undefined as an alternative to type: tag', () => { + const opt = { + runOnly: { + values: ['positive'] + } + }; + assert.equal(axe.utils.normalizeRunOptions(opt).runOnly.type, 'tag'); + }); + + it('allows runOnly as an array as an alternative to type: tag', () => { + const opt = { runOnly: ['positive', 'negative'] }; + const out = axe.utils.normalizeRunOptions(opt); + assert.equal(out.runOnly.type, 'tag'); + assert.deepEqual(out.runOnly.values, ['positive', 'negative']); + }); + + it('allows runOnly as an array as an alternative to type: rule', () => { + const opt = { runOnly: ['positive1', 'negative1'] }; + const out = axe.utils.normalizeRunOptions(opt); + assert.equal(out.runOnly.type, 'rule'); + assert.deepEqual(out.runOnly.values, ['positive1', 'negative1']); + }); + + it('allows runOnly as a string as an alternative to an array', () => { + const opt = { runOnly: 'positive1' }; + const out = axe.utils.normalizeRunOptions(opt); + assert.equal(out.runOnly.type, 'rule'); + assert.deepEqual(out.runOnly.values, ['positive1']); + }); + + it('throws an error if runOnly contains both rules and tags', () => { + assert.throws(() => { + axe.utils.normalizeRunOptions({ + runOnly: ['positive', 'negative1'] + }); + }); + }); + + it('defaults runOnly to type: tag', () => { + const opt = { runOnly: ['fakeTag'] }; + const out = axe.utils.normalizeRunOptions(opt); + assert.equal(out.runOnly.type, 'tag'); + assert.deepEqual(out.runOnly.values, ['fakeTag']); + }); + + it('throws an error runOnly.values not an array', () => { + assert.throws(() => { + axe.utils.normalizeRunOptions({ + runOnly: { + type: 'rule', + values: { badProp: 'badValue' } + } + }); + }); + }); + + it('throws an error runOnly.values an empty', () => { + assert.throws(() => { + axe.utils.normalizeRunOptions({ + runOnly: { + type: 'rule', + values: [] + } + }); + }); + }); + + it('throws an error runOnly.type is unknown', () => { + assert.throws(() => { + axe.utils.normalizeRunOptions({ + runOnly: { + type: 'something-else', + values: ['wcag2aa'] + } + }); + }); + }); + + it('throws an error when option.runOnly has an unknown rule', () => { + assert.throws(() => { + axe.utils.normalizeRunOptions({ + runOnly: { + type: 'rule', + values: ['frakeRule'] + } + }); + }); + }); + + it("doesn't throw an error when option.runOnly has an unknown tag", () => { + assert.doesNotThrow(() => { + axe.utils.normalizeRunOptions({ + runOnly: { + type: 'tags', + values: ['fakeTag'] + } + }); + }); + }); + + it('throws an error when option.rules has an unknown rule', () => { + assert.throws(() => { + axe.utils.normalizeRunOptions({ + rules: { + fakeRule: { enabled: false } + } + }); + }); + }); + + it('logs an issue when a tag is unknown', () => { + let message = ''; + axe.log = function (m) { + message = m; + }; + axe.utils.normalizeRunOptions({ + runOnly: { + type: 'tags', + values: ['unknown-tag'] + } + }); + assert.include(message, 'Could not find tags'); + }); + + it('logs no issues for unknown WCAG level tags', () => { + let message = ''; + axe.log = function (m) { + message = m; + }; + axe.utils.normalizeRunOptions({ + runOnly: { + type: 'tags', + values: ['wcag23aaa'] + } + }); + assert.isEmpty(message); + }); + + it('logs an issue when a tag is unknown, together with a wcag level tag', () => { + let message = ''; + axe.log = function (m) { + message = m; + }; + axe.utils.normalizeRunOptions({ + runOnly: { + type: 'tags', + values: ['wcag23aaa', 'unknwon-tag'] + } + }); + assert.include(message, 'Could not find tags'); + }); +}); From c395424a64a74cadd525cb088476459f59d5e78d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:37:27 -0700 Subject: [PATCH 12/62] chore: bump the npm-low-risk group across 1 directory with 8 updates (#5008) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the npm-low-risk group with 8 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) | `7.28.5` | `7.28.6` | | [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) | `7.28.5` | `7.28.6` | | [@babel/runtime-corejs3](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime-corejs3) | `7.28.4` | `7.28.6` | | [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) | `3.47.0` | `3.48.0` | | [globals](https://github.com/sindresorhus/globals) | `17.1.0` | `17.2.0` | | [jsdom](https://github.com/jsdom/jsdom) | `27.3.0` | `27.4.0` | | [prettier](https://github.com/prettier/prettier) | `3.7.4` | `3.8.1` | | [selenium-webdriver](https://github.com/SeleniumHQ/selenium) | `4.39.0` | `4.40.0` | Updates `@babel/core` from 7.28.5 to 7.28.6
Release notes

Sourced from @​babel/core's releases.

v7.28.6 (2026-01-12)

Thanks @​kadhirash and @​kolvian for your first PRs!

:bug: Bug Fix

  • babel-cli, babel-code-frame, babel-core, babel-helper-check-duplicate-nodes, babel-helper-fixtures, babel-helper-plugin-utils, babel-node, babel-plugin-transform-flow-comments, babel-plugin-transform-modules-commonjs, babel-plugin-transform-property-mutators, babel-preset-env, babel-traverse, babel-types
  • babel-plugin-transform-regenerator
  • babel-plugin-transform-react-jsx

:nail_care: Polish

  • babel-core, babel-standalone

:house: Internal

  • babel-plugin-bugfix-v8-static-class-fields-redefine-readonly, babel-plugin-proposal-decorators, babel-plugin-proposal-import-attributes-to-assertions, babel-plugin-proposal-import-wasm-source, babel-plugin-syntax-async-do-expressions, babel-plugin-syntax-decorators, babel-plugin-syntax-destructuring-private, babel-plugin-syntax-do-expressions, babel-plugin-syntax-explicit-resource-management, babel-plugin-syntax-export-default-from, babel-plugin-syntax-flow, babel-plugin-syntax-function-bind, babel-plugin-syntax-function-sent, babel-plugin-syntax-import-assertions, babel-plugin-syntax-import-attributes, babel-plugin-syntax-import-defer, babel-plugin-syntax-import-source, babel-plugin-syntax-jsx, babel-plugin-syntax-module-blocks, babel-plugin-syntax-optional-chaining-assign, babel-plugin-syntax-partial-application, babel-plugin-syntax-pipeline-operator, babel-plugin-syntax-throw-expressions, babel-plugin-syntax-typescript, babel-plugin-transform-async-generator-functions, babel-plugin-transform-async-to-generator, babel-plugin-transform-class-properties, babel-plugin-transform-class-static-block, babel-plugin-transform-dotall-regex, babel-plugin-transform-duplicate-named-capturing-groups-regex, babel-plugin-transform-explicit-resource-management, babel-plugin-transform-exponentiation-operator, babel-plugin-transform-json-strings, babel-plugin-transform-logical-assignment-operators, babel-plugin-transform-nullish-coalescing-operator, babel-plugin-transform-numeric-separator, babel-plugin-transform-object-rest-spread, babel-plugin-transform-optional-catch-binding, babel-plugin-transform-optional-chaining, babel-plugin-transform-private-methods, babel-plugin-transform-private-property-in-object, babel-plugin-transform-regexp-modifiers, babel-plugin-transform-unicode-property-regex, babel-plugin-transform-unicode-sets-regex

:running_woman: Performance

  • babel-plugin-transform-react-jsx

Committers: 7

Commits

Updates `@babel/preset-env` from 7.28.5 to 7.28.6
Release notes

Sourced from @​babel/preset-env's releases.

v7.28.6 (2026-01-12)

Thanks @​kadhirash and @​kolvian for your first PRs!

:bug: Bug Fix

  • babel-cli, babel-code-frame, babel-core, babel-helper-check-duplicate-nodes, babel-helper-fixtures, babel-helper-plugin-utils, babel-node, babel-plugin-transform-flow-comments, babel-plugin-transform-modules-commonjs, babel-plugin-transform-property-mutators, babel-preset-env, babel-traverse, babel-types
  • babel-plugin-transform-regenerator
  • babel-plugin-transform-react-jsx

:nail_care: Polish

  • babel-core, babel-standalone

:house: Internal

  • babel-plugin-bugfix-v8-static-class-fields-redefine-readonly, babel-plugin-proposal-decorators, babel-plugin-proposal-import-attributes-to-assertions, babel-plugin-proposal-import-wasm-source, babel-plugin-syntax-async-do-expressions, babel-plugin-syntax-decorators, babel-plugin-syntax-destructuring-private, babel-plugin-syntax-do-expressions, babel-plugin-syntax-explicit-resource-management, babel-plugin-syntax-export-default-from, babel-plugin-syntax-flow, babel-plugin-syntax-function-bind, babel-plugin-syntax-function-sent, babel-plugin-syntax-import-assertions, babel-plugin-syntax-import-attributes, babel-plugin-syntax-import-defer, babel-plugin-syntax-import-source, babel-plugin-syntax-jsx, babel-plugin-syntax-module-blocks, babel-plugin-syntax-optional-chaining-assign, babel-plugin-syntax-partial-application, babel-plugin-syntax-pipeline-operator, babel-plugin-syntax-throw-expressions, babel-plugin-syntax-typescript, babel-plugin-transform-async-generator-functions, babel-plugin-transform-async-to-generator, babel-plugin-transform-class-properties, babel-plugin-transform-class-static-block, babel-plugin-transform-dotall-regex, babel-plugin-transform-duplicate-named-capturing-groups-regex, babel-plugin-transform-explicit-resource-management, babel-plugin-transform-exponentiation-operator, babel-plugin-transform-json-strings, babel-plugin-transform-logical-assignment-operators, babel-plugin-transform-nullish-coalescing-operator, babel-plugin-transform-numeric-separator, babel-plugin-transform-object-rest-spread, babel-plugin-transform-optional-catch-binding, babel-plugin-transform-optional-chaining, babel-plugin-transform-private-methods, babel-plugin-transform-private-property-in-object, babel-plugin-transform-regexp-modifiers, babel-plugin-transform-unicode-property-regex, babel-plugin-transform-unicode-sets-regex

:running_woman: Performance

  • babel-plugin-transform-react-jsx

Committers: 7

Commits

Updates `@babel/runtime-corejs3` from 7.28.4 to 7.28.6
Release notes

Sourced from @​babel/runtime-corejs3's releases.

v7.28.6 (2026-01-12)

Thanks @​kadhirash and @​kolvian for your first PRs!

:bug: Bug Fix

  • babel-cli, babel-code-frame, babel-core, babel-helper-check-duplicate-nodes, babel-helper-fixtures, babel-helper-plugin-utils, babel-node, babel-plugin-transform-flow-comments, babel-plugin-transform-modules-commonjs, babel-plugin-transform-property-mutators, babel-preset-env, babel-traverse, babel-types
  • babel-plugin-transform-regenerator
  • babel-plugin-transform-react-jsx

:nail_care: Polish

  • babel-core, babel-standalone

:house: Internal

  • babel-plugin-bugfix-v8-static-class-fields-redefine-readonly, babel-plugin-proposal-decorators, babel-plugin-proposal-import-attributes-to-assertions, babel-plugin-proposal-import-wasm-source, babel-plugin-syntax-async-do-expressions, babel-plugin-syntax-decorators, babel-plugin-syntax-destructuring-private, babel-plugin-syntax-do-expressions, babel-plugin-syntax-explicit-resource-management, babel-plugin-syntax-export-default-from, babel-plugin-syntax-flow, babel-plugin-syntax-function-bind, babel-plugin-syntax-function-sent, babel-plugin-syntax-import-assertions, babel-plugin-syntax-import-attributes, babel-plugin-syntax-import-defer, babel-plugin-syntax-import-source, babel-plugin-syntax-jsx, babel-plugin-syntax-module-blocks, babel-plugin-syntax-optional-chaining-assign, babel-plugin-syntax-partial-application, babel-plugin-syntax-pipeline-operator, babel-plugin-syntax-throw-expressions, babel-plugin-syntax-typescript, babel-plugin-transform-async-generator-functions, babel-plugin-transform-async-to-generator, babel-plugin-transform-class-properties, babel-plugin-transform-class-static-block, babel-plugin-transform-dotall-regex, babel-plugin-transform-duplicate-named-capturing-groups-regex, babel-plugin-transform-explicit-resource-management, babel-plugin-transform-exponentiation-operator, babel-plugin-transform-json-strings, babel-plugin-transform-logical-assignment-operators, babel-plugin-transform-nullish-coalescing-operator, babel-plugin-transform-numeric-separator, babel-plugin-transform-object-rest-spread, babel-plugin-transform-optional-catch-binding, babel-plugin-transform-optional-chaining, babel-plugin-transform-private-methods, babel-plugin-transform-private-property-in-object, babel-plugin-transform-regexp-modifiers, babel-plugin-transform-unicode-property-regex, babel-plugin-transform-unicode-sets-regex

:running_woman: Performance

  • babel-plugin-transform-react-jsx

Committers: 7

v7.28.5 (2025-10-23)

Thank you @​CO0Ki3, @​Olexandr88, and @​youthfulhps for your first PRs!

:eyeglasses: Spec Compliance

:bug: Bug Fix

  • babel-plugin-proposal-destructuring-private
  • babel-parser
  • babel-plugin-proposal-discard-binding, babel-plugin-transform-destructuring

... (truncated)

Commits
Maintainer changes

This version was pushed to npm by [GitHub Actions](https://www.npmjs.com/~GitHub Actions), a new releaser for @​babel/runtime-corejs3 since your current version.


Updates `core-js` from 3.47.0 to 3.48.0
Changelog

Sourced from core-js's changelog.

3.48.0 - 2026.01.21

  • Changes v3.47.0...v3.48.0 (126 commits)
  • Map upsert proposal:
    • Built-ins:
      • Map.prototype.getOrInsert
      • Map.prototype.getOrInsertComputed
      • WeakMap.prototype.getOrInsert
      • WeakMap.prototype.getOrInsertComputed
    • Moved to stable ES, January 2026 TC39 meeting
    • Added es. namespace modules, /es/ and /stable/ namespaces entries
  • Use CreateDataProperty / CreateDataPropertyOrThrow in some missed cases, #1497
  • Minor fix / optimization in the RegExp constructor (NCG and dotAll) polyfill
  • Added some more workarounds for a Safari < 13 bug with silent ignore of non-writable array .length
  • Added detection of a Webkit bug: Iterator.prototype.flatMap throws on iterator without return method
  • Added detection of a V8 ~ Chromium < 144 bug: Uint8Array.prototype.setFromHex throws an error on length-tracking views over ResizableArrayBuffer
  • Compat data improvements:
Commits
  • 5d657da v3.48.0
  • 5644e73 Add bug detection for Uint8Array.prototype.setFromHex
  • 804a10e Merge pull request #1501 from zloirock/flat-map-fix
  • 45d7fe3 Add bug detection to Iterator flatMap method
  • efd9c2f move Map upsert proposal to stable ES
  • dcb938d update the year and normalize the copyright wording
  • 5454a5d add some more workarounds for a Safari < 13 bug with silent ignore of non-wri...
  • 73d4b6c add some more createProperty cases
  • 9c89290 fix: spec compliance for Array.prototype.flat and flatMap
  • e3774ce fix: use createProperty in Array.prototype.filter
  • Additional commits viewable in compare view

Updates `globals` from 17.1.0 to 17.2.0
Release notes

Sourced from globals's releases.

v17.2.0

  • jasmine: Add throwUnless and throwUnlessAsync globals (#335) 97f23a7

https://github.com/sindresorhus/globals/compare/v17.1.0...v17.2.0

Commits

Updates `jsdom` from 27.3.0 to 27.4.0
Release notes

Sourced from jsdom's releases.

Version 27.4.0

  • Added TextEncoder and TextDecoder.
  • Improved decoding of HTML bytes by using the new @exodus/bytes package; it is now much more correct. (ChALkeR)
  • Improved decoding of XML bytes to use UTF-8 more often, instead of sniffing for <meta charset> or using the parent frame's encoding.
  • Fixed a memory leak when Ranges were used and then the elements referred to by those ranges were removed.
Changelog

Sourced from jsdom's changelog.

27.4.0

  • Added TextEncoder and TextDecoder.
  • Improved decoding of HTML bytes by using the new @exodus/bytes package; it is now much more correct. (ChALkeR)
  • Improved decoding of XML bytes to use UTF-8 more often, instead of sniffing for <meta charset> or using the parent frame's encoding.
  • Fixed a memory leak when Ranges were used and then the elements referred to by those ranges were removed.
Commits
  • 098d16d Version 27.4.0
  • 1cd029e Improve asciiLowercase/asciiUppercase performance
  • 83fcb62 Implement TextEncoder and TextDecoder; improve XML decoding
  • ddad97d Switch from iconv-lite to exodus/bytes for decoding
  • 25cb2a1 Use weak references for ranges
  • ed4f5ed Add currently-failing CSS regression tests
  • See full diff in compare view

Updates `prettier` from 3.7.4 to 3.8.1
Release notes

Sourced from prettier's releases.

3.8.1

🔗 Changelog

3.8.0

  • Support Angular v21.1

diff

🔗 Release note "Prettier 3.8: Support for Angular v21.1"

Changelog

Sourced from prettier's changelog.

3.8.1

diff

Include available printers in plugin type declarations (#18706 by @​porada)

// Input
import * as prettierPluginEstree from
"prettier/plugins/estree";

// Prettier 3.8.0 // Property 'printers' does not exist on type 'typeof import("prettier/plugins/estree")'. ts(2339) prettierPluginEstree.printers.estree; //=> any

// Prettier 3.8.1 prettierPluginEstree.printers.estree; //=> Printer prettierPluginEstree.printers["estree-json"]; //=> Printer

3.8.0

diff

🔗 Release Notes

Commits

Updates `selenium-webdriver` from 4.39.0 to 4.40.0
Release notes

Sourced from selenium-webdriver's releases.

Selenium 4.40.0

Detailed Changelogs by Component

Java     |     Python     |     DotNet     |     Ruby     |     JavaScript

What's Changed

... (truncated)

Commits
  • b3333f1 [build] Prepare for release of Selenium 4.40 (#16931)
  • 1229133 [build] manual trigger job needs an approval step to limit
  • 1cc4bc7 [build] remove ruby remote tests from CI
  • bcd0976 [build] use rulesets to restrict and unrestrict trunk during release window (...
  • 47dbd0d [dotnet] [bidi] Expose Input module in root BiDi class (#16940)
  • 1fefb89 [dotnet] [bidi] FileDialogOpened event in Input module (#16934)
  • 691fb1c [grid] Improve race conditions in Grid session distribution (#16939)
  • aaeb4a5 [build] ensure compatible edge browser and driver when pinning
  • 492b6c4 [dotnet] fix syntax issue with bazel target preventing build
  • 631c5b0 [java] sanitize descriptions before using them in JavaDoc comments
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 1368 +++++++++++++++++++++------------------------ 1 file changed, 651 insertions(+), 717 deletions(-) diff --git a/package-lock.json b/package-lock.json index e8f8aa471..6e7580ca7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -161,13 +161,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -176,29 +175,29 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -215,13 +214,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz", + "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", "dev": true, "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -243,13 +242,12 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -275,17 +273,17 @@ "dev": true }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", - "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.3", + "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "engines": { @@ -296,14 +294,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", - "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "regexpu-core": "^6.2.0", + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "engines": { @@ -362,42 +359,40 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -420,11 +415,10 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -434,7 +428,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", @@ -448,15 +441,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -509,40 +501,39 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", - "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -618,13 +609,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", - "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -665,13 +656,12 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -681,13 +671,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -741,14 +730,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", - "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -758,14 +747,13 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { @@ -792,12 +780,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", - "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -807,14 +795,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -824,13 +811,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", - "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -840,17 +827,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", - "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.4" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -860,14 +847,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -893,14 +879,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -926,14 +911,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -959,13 +943,13 @@ } }, "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", - "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -975,12 +959,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", - "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1041,13 +1025,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1073,12 +1056,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", - "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1121,14 +1104,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1206,13 +1188,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1222,13 +1203,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1238,16 +1218,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", - "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.4" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1274,13 +1254,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1290,12 +1269,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", - "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { @@ -1321,14 +1300,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1338,15 +1316,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1372,12 +1349,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", - "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1387,14 +1364,13 @@ } }, "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1436,13 +1412,12 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { @@ -1517,14 +1492,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1551,14 +1525,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1568,75 +1541,75 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", - "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.6.tgz", + "integrity": "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/compat-data": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.0", - "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.6", + "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.5", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-classes": "^7.28.4", - "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6", "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.28.5", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-modules-systemjs": "^7.28.5", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.4", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.28.5", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.4", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.6", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", @@ -1666,9 +1639,9 @@ } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz", - "integrity": "sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.6.tgz", + "integrity": "sha512-kz2fAQ5UzjV7X7D3ySxmj3vRq89dTpqOZWv76Z6pNPztkwb/0Yj1Mtx1xFrYj6mbIHysxtBot8J4o0JLCblcFw==", "dev": true, "dependencies": { "core-js-pure": "^3.43.0" @@ -1678,32 +1651,31 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -1711,9 +1683,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -2015,6 +1987,23 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@exodus/bytes": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.11.0.tgz", + "integrity": "sha512-wO3vd8nsEHdumsXrjGO/v4p6irbg7hy9kvIeR6i2AwylZSk4HJdWgL0FNaVquW1+AweJcdvU1IEpuIWk/WaPnA==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@hapi/address": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz", @@ -4234,9 +4223,9 @@ } }, "node_modules/core-js": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", - "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", "dev": true, "hasInstallScript": true, "funding": { @@ -6376,9 +6365,9 @@ } }, "node_modules/globals": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.1.0.tgz", - "integrity": "sha512-8HoIcWI5fCvG5NADj4bDav+er9B9JMj2vyL2pI8D0eismKyUvPLTSs+Ln3wqhwcp306i73iyVnEKx3F6T47TGw==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.2.0.tgz", + "integrity": "sha512-tovnCz/fEq+Ripoq+p/gN1u7l6A7wwkoBT9pRCzTHzsD/LvADIzXZdjmRymh5Ztf0DYC3Rwg5cZRYjxzBmzbWg==", "dev": true, "engines": { "node": ">=18" @@ -7985,17 +7974,18 @@ } }, "node_modules/jsdom": { - "version": "27.3.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.3.0.tgz", - "integrity": "sha512-GtldT42B8+jefDUC4yUKAvsaOrH7PDHmZxZXNgF2xMmymjUbRYJvpAybZAKEmXDGTM0mCsz8duOa4vTm5AY2Kg==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz", + "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", "dev": true, "dependencies": { "@acemir/cssom": "^0.9.28", "@asamuzakjp/dom-selector": "^6.7.6", + "@exodus/bytes": "^1.6.0", "cssstyle": "^5.3.4", "data-urls": "^6.0.0", "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^4.0.0", + "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", @@ -8005,7 +7995,6 @@ "tough-cookie": "^6.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.0", - "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^15.1.0", "ws": "^8.18.3", @@ -8024,39 +8013,15 @@ } }, "node_modules/jsdom/node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^3.1.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/jsdom/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jsdom/node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "dev": true, "dependencies": { - "iconv-lite": "0.6.3" + "@exodus/bytes": "^1.6.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/jsesc": { @@ -10595,9 +10560,9 @@ } }, "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -11064,9 +11029,9 @@ "dev": true }, "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "dev": true, "dependencies": { "regenerate": "^1.4.2" @@ -11093,17 +11058,17 @@ } }, "node_modules/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", "dev": true, "dependencies": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", + "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", + "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "unicode-match-property-value-ecmascript": "^2.2.1" }, "engines": { "node": ">=4" @@ -11116,29 +11081,17 @@ "dev": true }, "node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", "dev": true, "dependencies": { - "jsesc": "~3.0.2" + "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", @@ -11366,9 +11319,9 @@ "dev": true }, "node_modules/selenium-webdriver": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.39.0.tgz", - "integrity": "sha512-NAs9jCU+UeZ/ZmRb8R6zOp7N8eMklefdBYASnaRmCNXdgFE8w3OCxxZmLixkwqnGDHY5VF7hCulfw1Mls43N/A==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.40.0.tgz", + "integrity": "sha512-dU0QbnVKdPmoNP8OtMCazRdtU2Ux6Wl4FEpG1iwUbDeajJK1dBAywBLrC1D7YFRtogHzN96AbXBgBAJaarcysw==", "dev": true, "funding": [ { @@ -12695,18 +12648,18 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "dev": true, "engines": { "node": ">=4" } }, "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "dev": true, "engines": { "node": ">=4" @@ -13418,37 +13371,37 @@ } }, "@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true }, "@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -13458,13 +13411,13 @@ } }, "@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz", + "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", "dev": true, "requires": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -13480,12 +13433,12 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "requires": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -13510,28 +13463,28 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", - "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.3", + "@babel/traverse": "^7.28.6", "semver": "^6.3.1" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", - "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "regexpu-core": "^6.2.0", + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", "semver": "^6.3.1" } }, @@ -13572,34 +13525,34 @@ "dev": true }, "@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, "requires": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" } }, "@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "requires": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" } }, "@babel/helper-optimise-call-expression": { @@ -13612,9 +13565,9 @@ } }, "@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true }, "@babel/helper-remap-async-to-generator": { @@ -13629,14 +13582,14 @@ } }, "@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.6" } }, "@babel/helper-skip-transparent-expression-wrappers": { @@ -13668,33 +13621,33 @@ "dev": true }, "@babel/helper-wrap-function": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", - "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", "dev": true, "requires": { - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "requires": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "requires": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" } }, "@babel/plugin-bugfix-firefox-class-in-computed-class-key": { @@ -13737,13 +13690,13 @@ } }, "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", - "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" } }, "@babel/plugin-proposal-object-rest-spread": { @@ -13767,21 +13720,21 @@ "requires": {} }, "@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-syntax-object-rest-spread": { @@ -13813,24 +13766,24 @@ } }, "@babel/plugin-transform-async-generator-functions": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", - "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.29.0" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1" } }, @@ -13844,56 +13797,56 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", - "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-class-static-block": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", - "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-classes": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", - "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.4" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" } }, "@babel/plugin-transform-destructuring": { @@ -13907,13 +13860,13 @@ } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-duplicate-keys": { @@ -13926,13 +13879,13 @@ } }, "@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-dynamic-import": { @@ -13945,22 +13898,22 @@ } }, "@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", - "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", - "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-export-namespace-from": { @@ -13994,12 +13947,12 @@ } }, "@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-literals": { @@ -14012,12 +13965,12 @@ } }, "@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", - "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-member-expression-literals": { @@ -14040,13 +13993,13 @@ } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-modules-systemjs": { @@ -14091,34 +14044,34 @@ } }, "@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-object-rest-spread": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", - "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", "dev": true, "requires": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.4" + "@babel/traverse": "^7.28.6" } }, "@babel/plugin-transform-object-super": { @@ -14132,21 +14085,21 @@ } }, "@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-optional-chaining": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", - "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" } }, @@ -14160,24 +14113,24 @@ } }, "@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-property-literals": { @@ -14190,22 +14143,22 @@ } }, "@babel/plugin-transform-regenerator": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", - "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-reserved-words": { @@ -14227,12 +14180,12 @@ } }, "@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" } }, @@ -14273,13 +14226,13 @@ } }, "@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-unicode-regex": { @@ -14293,85 +14246,85 @@ } }, "@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/preset-env": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", - "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.6.tgz", + "integrity": "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==", "dev": true, "requires": { - "@babel/compat-data": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/compat-data": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.0", - "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.6", + "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.5", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-classes": "^7.28.4", - "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6", "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.28.5", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-modules-systemjs": "^7.28.5", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.4", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.28.5", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.4", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.6", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", @@ -14392,44 +14345,44 @@ } }, "@babel/runtime-corejs3": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz", - "integrity": "sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.6.tgz", + "integrity": "sha512-kz2fAQ5UzjV7X7D3ySxmj3vRq89dTpqOZWv76Z6pNPztkwb/0Yj1Mtx1xFrYj6mbIHysxtBot8J4o0JLCblcFw==", "dev": true, "requires": { "core-js-pure": "^3.43.0" } }, "@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "requires": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "requires": { "@babel/helper-string-parser": "^7.27.1", @@ -14587,6 +14540,13 @@ "levn": "^0.4.1" } }, + "@exodus/bytes": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.11.0.tgz", + "integrity": "sha512-wO3vd8nsEHdumsXrjGO/v4p6irbg7hy9kvIeR6i2AwylZSk4HJdWgL0FNaVquW1+AweJcdvU1IEpuIWk/WaPnA==", + "dev": true, + "requires": {} + }, "@hapi/address": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz", @@ -16321,9 +16281,9 @@ "dev": true }, "core-js": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", - "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", "dev": true }, "core-js-compat": { @@ -17922,9 +17882,9 @@ } }, "globals": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.1.0.tgz", - "integrity": "sha512-8HoIcWI5fCvG5NADj4bDav+er9B9JMj2vyL2pI8D0eismKyUvPLTSs+Ln3wqhwcp306i73iyVnEKx3F6T47TGw==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.2.0.tgz", + "integrity": "sha512-tovnCz/fEq+Ripoq+p/gN1u7l6A7wwkoBT9pRCzTHzsD/LvADIzXZdjmRymh5Ztf0DYC3Rwg5cZRYjxzBmzbWg==", "dev": true }, "globule": { @@ -19083,17 +19043,18 @@ } }, "jsdom": { - "version": "27.3.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.3.0.tgz", - "integrity": "sha512-GtldT42B8+jefDUC4yUKAvsaOrH7PDHmZxZXNgF2xMmymjUbRYJvpAybZAKEmXDGTM0mCsz8duOa4vTm5AY2Kg==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz", + "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", "dev": true, "requires": { "@acemir/cssom": "^0.9.28", "@asamuzakjp/dom-selector": "^6.7.6", + "@exodus/bytes": "^1.6.0", "cssstyle": "^5.3.4", "data-urls": "^6.0.0", "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^4.0.0", + "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", @@ -19103,7 +19064,6 @@ "tough-cookie": "^6.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.0", - "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^15.1.0", "ws": "^8.18.3", @@ -19111,30 +19071,12 @@ }, "dependencies": { "html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "dev": true, "requires": { - "whatwg-encoding": "^3.1.1" - } - }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, - "whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "dev": true, - "requires": { - "iconv-lite": "0.6.3" + "@exodus/bytes": "^1.6.0" } } } @@ -21046,9 +20988,9 @@ "dev": true }, "prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true }, "pretty-bytes": { @@ -21406,9 +21348,9 @@ "dev": true }, "regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "dev": true, "requires": { "regenerate": "^1.4.2" @@ -21426,17 +21368,17 @@ } }, "regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", "dev": true, "requires": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", + "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", + "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "unicode-match-property-value-ecmascript": "^2.2.1" } }, "regjsgen": { @@ -21446,20 +21388,12 @@ "dev": true }, "regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", "dev": true, "requires": { - "jsesc": "~3.0.2" - }, - "dependencies": { - "jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true - } + "jsesc": "~3.1.0" } }, "relateurl": { @@ -21635,9 +21569,9 @@ "dev": true }, "selenium-webdriver": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.39.0.tgz", - "integrity": "sha512-NAs9jCU+UeZ/ZmRb8R6zOp7N8eMklefdBYASnaRmCNXdgFE8w3OCxxZmLixkwqnGDHY5VF7hCulfw1Mls43N/A==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.40.0.tgz", + "integrity": "sha512-dU0QbnVKdPmoNP8OtMCazRdtU2Ux6Wl4FEpG1iwUbDeajJK1dBAywBLrC1D7YFRtogHzN96AbXBgBAJaarcysw==", "dev": true, "requires": { "@bazel/runfiles": "^6.5.0", @@ -22646,15 +22580,15 @@ } }, "unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "dev": true }, "unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "dev": true }, "union": { From 0d4e4e70aa9f46519eb6000744e043c058fd994e Mon Sep 17 00:00:00 2001 From: Chris Hutchins Date: Mon, 16 Feb 2026 09:49:15 -0500 Subject: [PATCH 13/62] feat(new-rule): check that aria-tab have an accessible name (#5001) Added a new rule to check that `aria-tab` nodes have an accessible name. Closes: [#4842](https://github.com/dequelabs/axe-core/issues/4842) --- doc/rule-descriptions.md | 1 + lib/rules/aria-tab-name.json | 30 +++++++ locales/_template.json | 4 + .../integration/full/all-rules/all-rules.html | 1 + .../full/isolated-env/isolated-env.html | 1 + .../rules/aria-tab-name/aria-tab-name.html | 27 ++++++ .../rules/aria-tab-name/aria-tab-name.json | 6 ++ .../aria-treeitem-name.html | 2 +- .../virtual-rules/aria-tab-name.js | 88 +++++++++++++++++++ 9 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 lib/rules/aria-tab-name.json create mode 100644 test/integration/rules/aria-tab-name/aria-tab-name.html create mode 100644 test/integration/rules/aria-tab-name/aria-tab-name.json create mode 100644 test/integration/virtual-rules/aria-tab-name.js diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index 8f586e983..4a44c49e0 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -32,6 +32,7 @@ | [aria-required-children](https://dequeuniversity.com/rules/axe/4.11/aria-required-children?application=RuleDescription) | Ensure elements with an ARIA role that require child roles contain them | Critical | cat.aria, wcag2a, wcag131, EN-301-549, EN-9.1.3.1, RGAAv4, RGAA-9.3.1 | failure, needs review | [bc4a75](https://act-rules.github.io/rules/bc4a75), [ff89c9](https://act-rules.github.io/rules/ff89c9) | | [aria-required-parent](https://dequeuniversity.com/rules/axe/4.11/aria-required-parent?application=RuleDescription) | Ensure elements with an ARIA role that require parent roles are contained by them | Critical | cat.aria, wcag2a, wcag131, EN-301-549, EN-9.1.3.1, RGAAv4, RGAA-9.3.1 | failure | [ff89c9](https://act-rules.github.io/rules/ff89c9) | | [aria-roles](https://dequeuniversity.com/rules/axe/4.11/aria-roles?application=RuleDescription) | Ensure all elements with a role attribute use a valid value | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2, RGAAv4, RGAA-7.1.1 | failure | [674b10](https://act-rules.github.io/rules/674b10) | +| [aria-tab-name](https://dequeuniversity.com/rules/axe/4.11/aria-tab-name?application=RuleDescription) | Ensure every ARIA tab node has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT, RGAAv4, RGAA-7.1.1 | failure, needs review | | | [aria-toggle-field-name](https://dequeuniversity.com/rules/axe/4.11/aria-toggle-field-name?application=RuleDescription) | Ensure every ARIA toggle field has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT, RGAAv4, RGAA-7.1.1 | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | | [aria-tooltip-name](https://dequeuniversity.com/rules/axe/4.11/aria-tooltip-name?application=RuleDescription) | Ensure every ARIA tooltip node has an accessible name | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | | | [aria-valid-attr-value](https://dequeuniversity.com/rules/axe/4.11/aria-valid-attr-value?application=RuleDescription) | Ensure all ARIA attributes have valid values | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2, RGAAv4, RGAA-7.1.1 | failure, needs review | [6a7281](https://act-rules.github.io/rules/6a7281) | diff --git a/lib/rules/aria-tab-name.json b/lib/rules/aria-tab-name.json new file mode 100644 index 000000000..97a959a79 --- /dev/null +++ b/lib/rules/aria-tab-name.json @@ -0,0 +1,30 @@ +{ + "id": "aria-tab-name", + "impact": "serious", + "selector": "[role=\"tab\"]", + "matches": "no-naming-method-matches", + "tags": [ + "cat.aria", + "wcag2a", + "wcag412", + "TTv5", + "TT5.c", + "EN-301-549", + "EN-9.4.1.2", + "ACT", + "RGAAv4", + "RGAA-7.1.1" + ], + "metadata": { + "description": "Ensure every ARIA tab node has an accessible name", + "help": "ARIA tab nodes must have an accessible name" + }, + "all": [], + "any": [ + "has-visible-text", + "aria-label", + "aria-labelledby", + "non-empty-title" + ], + "none": [] +} diff --git a/locales/_template.json b/locales/_template.json index 70203bd75..673574914 100644 --- a/locales/_template.json +++ b/locales/_template.json @@ -81,6 +81,10 @@ "description": "Ensure all elements with a role attribute use a valid value", "help": "ARIA roles used must conform to valid values" }, + "aria-tab-name": { + "description": "Ensure every ARIA tab node has an accessible name", + "help": "ARIA tab nodes must have an accessible name" + }, "aria-text": { "description": "Ensure role=\"text\" is used on elements with no focusable descendants", "help": "\"role=text\" should have no focusable descendants" diff --git a/test/integration/full/all-rules/all-rules.html b/test/integration/full/all-rules/all-rules.html index 8066cb00a..c55dbe29e 100644 --- a/test/integration/full/all-rules/all-rules.html +++ b/test/integration/full/all-rules/all-rules.html @@ -50,6 +50,7 @@
Item
+
Tab Name

Banana error

diff --git a/test/integration/full/isolated-env/isolated-env.html b/test/integration/full/isolated-env/isolated-env.html index 78482faf5..80608a54c 100644 --- a/test/integration/full/isolated-env/isolated-env.html +++ b/test/integration/full/isolated-env/isolated-env.html @@ -50,6 +50,7 @@
Item
+
Tab Name

Banana error

diff --git a/test/integration/rules/aria-tab-name/aria-tab-name.html b/test/integration/rules/aria-tab-name/aria-tab-name.html new file mode 100644 index 000000000..1ff621b34 --- /dev/null +++ b/test/integration/rules/aria-tab-name/aria-tab-name.html @@ -0,0 +1,27 @@ + + + + + + +Tab Name + +
+ + Tab Name + + +
+ + + + + + +
+ +
+ + + +
diff --git a/test/integration/rules/aria-tab-name/aria-tab-name.json b/test/integration/rules/aria-tab-name/aria-tab-name.json new file mode 100644 index 000000000..a74a35132 --- /dev/null +++ b/test/integration/rules/aria-tab-name/aria-tab-name.json @@ -0,0 +1,6 @@ +{ + "description": "aria-tab-name test", + "rule": "aria-tab-name", + "passes": [["#pass1"], ["#pass2"], ["#pass3"], ["#pass4"], ["#pass5"]], + "violations": [["#fail1"], ["#fail2"], ["#fail3"], ["#fail4"]] +} diff --git a/test/integration/rules/aria-treeitem-name/aria-treeitem-name.html b/test/integration/rules/aria-treeitem-name/aria-treeitem-name.html index cb641d2f7..b8cce5d79 100644 --- a/test/integration/rules/aria-treeitem-name/aria-treeitem-name.html +++ b/test/integration/rules/aria-treeitem-name/aria-treeitem-name.html @@ -11,7 +11,7 @@
-
+
diff --git a/test/integration/virtual-rules/aria-tab-name.js b/test/integration/virtual-rules/aria-tab-name.js new file mode 100644 index 000000000..4d8320f25 --- /dev/null +++ b/test/integration/virtual-rules/aria-tab-name.js @@ -0,0 +1,88 @@ +describe('aria-tab-name virtual-rule', function () { + it('should pass for aria-label', function () { + var node = new axe.SerialVirtualNode({ + nodeName: 'div', + attributes: { + role: 'tab', + 'aria-label': 'tabname' + } + }); + node.parent = null; + + var results = axe.runVirtualRule('aria-tab-name', node); + + assert.lengthOf(results.passes, 1); + assert.lengthOf(results.violations, 0); + assert.lengthOf(results.incomplete, 0); + }); + + it('should incomplete for aria-labelledby', function () { + var node = new axe.SerialVirtualNode({ + nodeName: 'div', + attributes: { + role: 'tab', + 'aria-labelledby': 'tabname' + } + }); + node.parent = null; + + var results = axe.runVirtualRule('aria-tab-name', node); + + assert.lengthOf(results.passes, 0); + assert.lengthOf(results.violations, 0); + assert.lengthOf(results.incomplete, 1); + }); + + it('should pass for title', function () { + var node = new axe.SerialVirtualNode({ + nodeName: 'div', + attributes: { + role: 'tab', + title: 'tabname' + } + }); + + node.children = []; + node.parent = null; + + var results = axe.runVirtualRule('aria-tab-name', node); + + assert.lengthOf(results.passes, 1); + assert.lengthOf(results.violations, 0); + assert.lengthOf(results.incomplete, 0); + }); + + it('should fail when aria-label contains only whitespace', function () { + var node = new axe.SerialVirtualNode({ + nodeName: 'div', + attributes: { + role: 'tab', + 'aria-label': ' \t \n ' + } + }); + node.children = []; + + var results = axe.runVirtualRule('aria-tab-name', node); + + assert.lengthOf(results.passes, 0); + assert.lengthOf(results.violations, 1); + assert.lengthOf(results.incomplete, 0); + }); + + it('should fail when title is empty', function () { + var node = new axe.SerialVirtualNode({ + nodeName: 'div', + attributes: { + role: 'tab', + title: '' + } + }); + node.children = []; + + var results = axe.runVirtualRule('aria-tab-name', node); + + assert.lengthOf(results.passes, 0); + assert.lengthOf(results.violations, 1); + assert.lengthOf(results.incomplete, 0); + }); +}); From 0281fa16f7110b793ac8b3b5b46f93e81be75ee4 Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Wed, 18 Feb 2026 12:46:32 +0100 Subject: [PATCH 14/62] fix(DqElement): avoid calling constructors with cloneNode (#5013) Closes: https://github.com/dequelabs/axe-core/issues/4996 - **Avoid calling cloneNode when retrieving source** - Add a new utils.getElementSource (which works even if the tree is not constructed - Made sure getElementSource works with namespaces and non-node elements - Check each attribute if it fits in the truncated source, instead of stopping at the first that doesn't fit --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- axe.d.ts | 4 + lib/core/utils/dq-element.js | 92 +--------- lib/core/utils/get-element-source.js | 101 +++++++++++ lib/core/utils/index.js | 1 + test/core/utils/dq-element.js | 90 ---------- test/core/utils/get-element-source.js | 233 ++++++++++++++++++++++++++ 6 files changed, 340 insertions(+), 181 deletions(-) create mode 100644 lib/core/utils/get-element-source.js create mode 100644 test/core/utils/get-element-source.js diff --git a/axe.d.ts b/axe.d.ts index c679dd554..05cae62a5 100644 --- a/axe.d.ts +++ b/axe.d.ts @@ -470,6 +470,10 @@ declare namespace axe { } interface Utils { + getElementSource: ( + element: Node | null | undefined, + options?: { maxLength?: number; attrLimit?: number } + ) => string; getFrameContexts: ( context?: ElementContext, options?: RunOptions diff --git a/lib/core/utils/dq-element.js b/lib/core/utils/dq-element.js index 556d625aa..de6480ebf 100644 --- a/lib/core/utils/dq-element.js +++ b/lib/core/utils/dq-element.js @@ -5,100 +5,10 @@ import getNodeFromTree from './get-node-from-tree'; import AbstractVirtualNode from '../base/virtual-node/abstract-virtual-node'; import cache from '../base/cache'; import memoize from './memoize'; -import getNodeAttributes from './get-node-attributes'; -import VirtualNode from '../../core/base/virtual-node/virtual-node'; +import getSource from './get-element-source'; const CACHE_KEY = 'DqElm.RunOptions'; -function getOuterHtml(element) { - let source = element.outerHTML; - - if (!source && typeof window.XMLSerializer === 'function') { - source = new window.XMLSerializer().serializeToString(element); - } - - return source || ''; -} - -/** - * Truncates the outerHTML property of an element - * @param {Node} element the element node which needs to be truncated - */ - -export function truncateElement(element) { - const maxLen = 300; - const maxAttrNameOrValueLen = 20; - - const deepStr = getOuterHtml(element); - let vNode = getNodeFromTree(element); - if (!vNode) { - vNode = new VirtualNode(element); - } - const { nodeName } = vNode.props; - - if (deepStr.length < maxLen) { - return deepStr; - } - - const attributeStrList = []; - const shallowNode = element.cloneNode(false); - const elementNodeMap = getNodeAttributes(shallowNode); - - let str = getOuterHtml(shallowNode); - - if (str.length < maxLen) { - let attrString = ''; - for (const { name, value } of elementNodeMap) { - const attr = { name, value }; - attrString += ` ${attr.name}="${attr.value}"`; - } - - str = `<${nodeName}${attrString}>`; - return str; - } - let strLen = `<${nodeName}>`.length; - - for (const { name, value } of elementNodeMap) { - if (strLen > maxLen) { - break; - } - - const attr = { name, value }; - let attrName = attr.name; - let attrValue = attr.value; - - attrName = - attrName.length > maxAttrNameOrValueLen - ? attrName.substring(0, maxAttrNameOrValueLen) + '...' - : attrName; - attrValue = - attrValue.length > maxAttrNameOrValueLen - ? attrValue.substring(0, maxAttrNameOrValueLen) + '...' - : attrValue; - - const strAttr = `${attrName}="${attrValue}"`; - strLen += (' ' + strAttr).length; - attributeStrList.push(strAttr); - } - - str = `<${nodeName} ${attributeStrList.join(' ')}>`; - if (str.length > maxLen) { - str = str.substring(0, maxLen) + ' ...>'; - } else if (attributeStrList.length < elementNodeMap.length) { - str = str.substring(0, str.length - 1) + ' ...>'; - } - - return str; -} - -function getSource(element) { - if (!element) { - return ''; - } - - return truncateElement(element); -} - /** * "Serialized" `HTMLElement`. It will calculate the CSS selector, * grab the source (outerHTML) and offer an array for storing frame paths diff --git a/lib/core/utils/get-element-source.js b/lib/core/utils/get-element-source.js new file mode 100644 index 000000000..b30354c36 --- /dev/null +++ b/lib/core/utils/get-element-source.js @@ -0,0 +1,101 @@ +import getNodeAttributes from './get-node-attributes'; +import isXHTML from './is-xhtml'; + +/** + * Gets the truncated HTML source of an element, or nodeValue for non-element nodes + * @param {Node} node the DOM node (element, text, comment, etc.) + * @param {Object} options truncation options + * @param {Number} [options.maxLength=300] maximum length of the output + * @param {Number} [options.attrLimit=20] maximum length for attribute names and values + * @returns {String} The outerHTML, truncated representation, or nodeValue for non-elements + */ +export default function getElementSource( + node, + { maxLength = 300, attrLimit = 20 } = {} +) { + if (!node) { + return ''; + } + // non-element nodes + if (node.nodeType !== 1) { + const value = node.nodeValue ?? ''; + return truncate(value, maxLength); + } + + const deepStr = getOuterHtml(node); + if (deepStr.length > maxLength) { + return getTruncatedElementSource(node, { maxLength, attrLimit }); + } + + return deepStr; +} + +/** + * Gets the outerHTML of an element, using XMLSerializer as fallback for SVG/MathML + * @param {Element} element the DOM element + * @returns {String} The serialized HTML or empty string + */ +function getOuterHtml(element) { + let source = element.outerHTML; + if (!source && typeof window.XMLSerializer === 'function') { + source = new window.XMLSerializer().serializeToString(element); + } + return source || ''; +} + +/** + * Builds a truncated HTML representation of an element when outerHTML exceeds maxLength. + * Note: attribute order may differ from the original source as node.attributes order is not guaranteed. + * @param {Element} elm the DOM element + * @param {Object} options truncation options + * @param {Number} options.maxLength maximum length of the output + * @param {Number} options.attrLimit maximum length for attribute names and values + * @returns {String} Truncated opening tag (e.g. '
') + */ +function getTruncatedElementSource(elm, { maxLength, attrLimit }) { + const nodeName = isXHTML(elm.ownerDocument || document) + ? elm.nodeName + : elm.nodeName.toLowerCase(); + + // Get a mutable attribute map, and work out their rendered length + const nodeAttrs = Array.from(getNodeAttributes(elm)).map( + ({ name, value }) => ({ name, value }) + ); + const attrsLength = nodeAttrs.reduce((acc, { name, value }) => { + // 4 = space before name + equals sign + opening quote + closing quote + return acc + name.length + value.length + 4; + }, 0); + + // 2 = opening "<" + space before first attribute + if (2 + nodeName.length + attrsLength > maxLength) { + nodeAttrs.forEach(attr => { + attr.name = truncate(attr.name, attrLimit); + attr.value = truncate(attr.value, attrLimit); + }); + } + + let source = `<${nodeName}`; + let tagEnd = '>'; + const truncateEnd = ' ...>'; + // Must check every attribute: an attr that doesn't fit may be followed by one that does + for (const attr of nodeAttrs) { + const attrStr = ` ${attr.name}="${attr.value}"`; + if (source.length + attrStr.length > maxLength - truncateEnd.length) { + tagEnd = truncateEnd; + continue; + } + source += attrStr; + } + + return source + tagEnd; +} + +/** + * Truncates a string to max length, appending '...' when truncated + * @param {String} str the string to truncate + * @param {Number} attrLimit maximum length before truncation + * @returns {String} The original string or truncated version with '...' suffix + */ +function truncate(str, attrLimit) { + return str.length <= attrLimit ? str : str.substring(0, attrLimit) + '...'; +} diff --git a/lib/core/utils/index.js b/lib/core/utils/index.js index 691b25a17..5f19f8b44 100644 --- a/lib/core/utils/index.js +++ b/lib/core/utils/index.js @@ -42,6 +42,7 @@ export { default as getStandards } from './get-standards'; export { default as getStyleSheetFactory } from './get-stylesheet-factory'; export { default as getXpath } from './get-xpath'; export { default as getAncestry } from './get-ancestry'; +export { default as getElementSource } from './get-element-source'; export { default as injectStyle } from './inject-style'; export { default as isArrayLike } from './is-array-like'; export { diff --git a/test/core/utils/dq-element.js b/test/core/utils/dq-element.js index dd3fb4063..b360ee09a 100644 --- a/test/core/utils/dq-element.js +++ b/test/core/utils/dq-element.js @@ -46,96 +46,6 @@ describe('DqElement', () => { }); describe('source', () => { - it('should include the outerHTML of the element', () => { - const vNode = queryFixture('
Hello!
'); - const outerHTML = vNode.actualNode.outerHTML; - const result = new DqElement(vNode); - assert.equal(result.source, outerHTML); - }); - - it('should work with SVG elements', () => { - const vNode = queryFixture(''); - const result = new DqElement(vNode); - assert.equal(result.source, vNode.actualNode.outerHTML); - }); - - it('should work with MathML', () => { - const vNode = queryFixture( - '' + - 'x2' + - '' - ); - - const result = new DqElement(vNode); - assert.equal(result.source, vNode.actualNode.outerHTML); - }); - - it('should truncate large elements', () => { - let div = '
'; - for (let i = 0; i < 300; i++) { - div += i; - } - div += '
'; - const vNode = queryFixture(div); - const result = new DqElement(vNode); - assert.equal(result.source, '
'); - }); - - it('should truncate large attributes of large element', () => { - const el = document.createElement('div'); - let attributeName = 'data-'; - let attributeValue = ''; - for (let i = 0; i < 500; i++) { - attributeName += 'foo'; - attributeValue += i; - } - el.setAttribute(attributeName, attributeValue); - - const vNode = new DqElement(el); - assert.equal( - vNode.source, - `
` - ); - }); - - it('should remove attributes for a large element having a large number of attributes', () => { - let customElement = '
`; - const vNode = queryFixture(customElement); - const result = new DqElement(vNode); - const truncatedAttrCount = (result.source.match(/attr/g) || []).length; - assert.isBelow(truncatedAttrCount, 100); - assert.isAtLeast(truncatedAttrCount, 10); - }); - - it('should truncate a large element with long custom tag name', () => { - let longCustomElementTagName = new Array(300).join('b'); - let customElement = `<${longCustomElementTagName} id="target">A`; - const vNode = queryFixture(customElement); - const result = new DqElement(vNode); - assert.equal(result.source, `${customElement.substring(0, 300)} ...>`); - }); - - it('should not truncate attributes if children are long but attribute itself is within limits', () => { - let el = document.createElement('div'); - let attributeValue = ''; - let innerHtml = ''; - for (let i = 0; i < 50; i++) { - attributeValue += 'a'; - innerHtml += 'foobar'; - } - el.setAttribute('long-attribute', attributeValue); - el.innerHTML = innerHtml; - - const vNode = new DqElement(el); - assert.equal(vNode.source, `
`); - }); - it('should use spec object over passed element', () => { const vNode = queryFixture('
Hello!
'); const spec = { source: 'woot' }; diff --git a/test/core/utils/get-element-source.js b/test/core/utils/get-element-source.js new file mode 100644 index 000000000..13b4cd34c --- /dev/null +++ b/test/core/utils/get-element-source.js @@ -0,0 +1,233 @@ +describe('axe.utils.getElementSource', () => { + const getElementSource = axe.utils.getElementSource; + const queryFixture = axe.testUtils.queryFixture; + + afterEach(() => { + axe.reset(); + }); + + it('should be exposed to utils', () => { + assert.equal(typeof axe.utils.getElementSource, 'function'); + }); + + it('should return empty string for null/undefined element', () => { + assert.equal(getElementSource(null), ''); + assert.equal(getElementSource(undefined), ''); + }); + + it('should return nodeValue for text nodes', () => { + const vNode = queryFixture('
Hello world
'); + const textNode = vNode.actualNode.firstChild; + assert.equal(textNode.nodeType, 3); + assert.equal(getElementSource(textNode), 'Hello world'); + }); + + it('should return nodeValue for comment nodes', () => { + const vNode = queryFixture( + '
' + ); + const commentNode = vNode.actualNode.firstChild; + assert.equal(commentNode.nodeType, 8); + assert.equal(getElementSource(commentNode), ' example comment '); + }); + + it('should truncate long nodeValue for non-element nodes', () => { + const textNode = document.createTextNode('x'.repeat(500)); + const result = getElementSource(textNode, { maxLength: 50 }); + assert.equal(result, 'x'.repeat(50) + '...'); + }); + + it('should work without the virtual tree (element not in axe context)', () => { + const el = document.createElement('div'); + el.setAttribute('id', 'standalone'); + el.textContent = 'Hello'; + const result = getElementSource(el); + assert.equal(result, '
Hello
'); + }); + + it('should include the outerHTML of the element', () => { + const vNode = queryFixture('
Hello!
'); + const outerHTML = vNode.actualNode.outerHTML; + const result = getElementSource(vNode.actualNode); + assert.equal(result, outerHTML); + }); + + it('should work with SVG elements', () => { + const vNode = queryFixture(''); + const result = getElementSource(vNode.actualNode); + assert.equal(result, vNode.actualNode.outerHTML); + }); + + it('should work with MathML', () => { + const vNode = queryFixture( + '' + + 'x2' + + '' + ); + + const result = getElementSource(vNode.actualNode); + assert.equal(result, vNode.actualNode.outerHTML); + }); + + describe('XML namespaces', () => { + it('should work with SVG and xlink:href attribute', () => { + const vNode = queryFixture( + '' + + 'Link' + + '' + ); + const result = getElementSource(vNode.actualNode); + + assert.include(result, 'xlink:href'); + assert.include(result, '#section'); + assert.equal(result, vNode.actualNode.outerHTML); + }); + + it('should truncate SVG with namespaced attributes correctly', () => { + const vNode = queryFixture( + '' + + '' + + 'x'.repeat(400) + + '' + + '' + ); + const result = getElementSource(vNode.actualNode, { maxLength: 50 }); + assert.match(result, /$/); + }); + + it('should work with createElementNS for elements in SVG namespace', () => { + const svgNS = 'http://www.w3.org/2000/svg'; + const el = document.createElementNS(svgNS, 'rect'); + el.setAttributeNS(null, 'id', 'target'); + el.setAttribute('width', '100'); + el.setAttribute('height', '50'); + const result = getElementSource(el); + assert.include(result, 'rect'); + assert.include(result, 'id="target"'); + assert.include(result, 'width="100"'); + assert.include(result, 'height="50"'); + assert.equal(result, el.outerHTML); + }); + + it('should work with setAttributeNS for namespaced attributes', () => { + const xlinkNS = 'http://www.w3.org/1999/xlink'; + const svgNS = 'http://www.w3.org/2000/svg'; + const el = document.createElementNS(svgNS, 'image'); + el.setAttributeNS(xlinkNS, 'xlink:href', 'test.png'); + el.setAttributeNS(null, 'id', 'target'); + const result = getElementSource(el); + assert.include(result, 'xlink:href'); + assert.include(result, 'test.png'); + assert.include(result, 'id="target"'); + assert.equal(result, el.outerHTML); + }); + }); + + it('should truncate large elements', () => { + let div = '
'; + for (let i = 0; i < 300; i++) { + div += i; + } + div += '
'; + const vNode = queryFixture(div); + const result = getElementSource(vNode.actualNode); + assert.equal(result, '
'); + }); + + it('should truncate large attributes of large element', () => { + const el = document.createElement('div'); + let attributeName = 'data-'; + let attributeValue = ''; + for (let i = 0; i < 500; i++) { + attributeName += 'foo'; + attributeValue += i; + } + el.setAttribute(attributeName, attributeValue); + + const result = getElementSource(el); + assert.equal( + result, + `
` + ); + }); + + it('should remove attributes for a large element having a large number of attributes', () => { + let customElement = '
`; + + const vNode = queryFixture(customElement); + const result = getElementSource(vNode.actualNode); + const truncatedAttrCount = (result.match(/attr/g) || []).length; + assert.isBelow(truncatedAttrCount, 100); + assert.isAtLeast(truncatedAttrCount, 10); + }); + + it('should truncate a large element with long custom tag name', () => { + let longCustomElementTagName = new Array(300).join('b'); + let customElement = `<${longCustomElementTagName} id="target">A`; + const vNode = queryFixture(customElement); + const result = getElementSource(vNode.actualNode); + assert.equal(result, `${customElement.substring(0, 300)} ...>`); + }); + + it('should not truncate attributes if children are long but attribute itself is within limits', () => { + let el = document.createElement('div'); + let attributeValue = ''; + let innerHtml = ''; + for (let i = 0; i < 50; i++) { + attributeValue += 'a'; + innerHtml += 'foobar'; + } + el.setAttribute('long-attribute', attributeValue); + el.innerHTML = innerHtml; + + const result = getElementSource(el); + assert.equal(result, `
`); + }); + + describe('options', () => { + it('should respect custom maxLength', () => { + const vNode = queryFixture( + '
' + 'x'.repeat(200) + '
' + ); + const result = getElementSource(vNode.actualNode, { maxLength: 25 }); + assert.equal(result, '
'); + }); + + it('should respect custom attrLimit', () => { + const el = document.createElement('div'); + const longName = 'data-' + 'x'.repeat(300); + const longValue = 'y'.repeat(300); + el.setAttribute(longName, longValue); + + const result = getElementSource(el, { attrLimit: 10 }); + assert.equal(result, '
'); + }); + + it('should include later attributes that fit after skipping long ones', () => { + const vNode = queryFixture( + '
content
' + ); + const result = getElementSource(vNode.actualNode, { maxLength: 50 }); + assert.equal(result, '
'); + }); + + it('should use defaults when options is empty', () => { + let div = '
'; + for (let i = 0; i < 300; i++) { + div += i; + } + div += '
'; + const vNode = queryFixture(div); + const result = getElementSource(vNode.actualNode, {}); + assert.equal(result, '
'); + }); + }); +}); From 48ca9554e2f0400caeec55c09aa100cbb415422d Mon Sep 17 00:00:00 2001 From: KAMIHARA Takumi <65489256+camiha@users.noreply.github.com> Date: Wed, 18 Feb 2026 21:29:20 +0900 Subject: [PATCH 15/62] fix(aria): prevent getOwnedVirtual from returning duplicate nodes (#4987) When a child element is also referenced via aria-owns, getOwnedVirtual returned the same node twice. Filter aria-owns references that already exist in children using Set. Closes: #4840 --------- Co-authored-by: Wilco Fiers --- lib/commons/aria/get-owned-virtual.js | 10 ++++- test/commons/aria/get-owned-virtual.js | 56 ++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/lib/commons/aria/get-owned-virtual.js b/lib/commons/aria/get-owned-virtual.js index 5b24b7bdb..c65bc9ae9 100644 --- a/lib/commons/aria/get-owned-virtual.js +++ b/lib/commons/aria/get-owned-virtual.js @@ -18,7 +18,15 @@ function getOwnedVirtual(virtualNode) { const owns = idrefs(actualNode, 'aria-owns') .filter(element => !!element) .map(element => axe.utils.getNodeFromTree(element)); - return [...children, ...owns]; + + // Deduplicates by first occurrence to match browser accessibility tree behavior + // See: https://github.com/dequelabs/axe-core/pull/4987 + const uniqueOwns = owns.filter((own, index) => owns.indexOf(own) === index); + const nativeChildren = children.filter( + child => !uniqueOwns.includes(child) + ); + + return [...nativeChildren, ...uniqueOwns]; } return [...children]; diff --git a/test/commons/aria/get-owned-virtual.js b/test/commons/aria/get-owned-virtual.js index 6011c16af..a01277ca1 100644 --- a/test/commons/aria/get-owned-virtual.js +++ b/test/commons/aria/get-owned-virtual.js @@ -37,6 +37,62 @@ describe('aria.getOwnedVirtual', function () { assert.equal(owned[3].actualNode.nodeName.toUpperCase(), 'H4'); }); + it('does not return duplicate when child is also aria-owned', function () { + fixtureSetup( + '
' + + '' + + '
' + ); + var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + var owned = aria.getOwnedVirtual(target); + assert.lengthOf(owned, 1); + assert.equal(owned[0].actualNode.id, 'foo'); + }); + + it('does not return duplicate when same ID appears multiple times in aria-owns', function () { + fixtureSetup( + '
' + + '
' + + '
A
' + + '
B
' + ); + var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + var owned = aria.getOwnedVirtual(target); + assert.lengthOf(owned, 2); + assert.equal(owned[0].actualNode.id, 'a'); + assert.equal(owned[1].actualNode.id, 'b'); + }); + + it('moves aria-owned child to the end', function () { + fixtureSetup( + '
' + + '
A
' + + '
B
' + + '
' + ); + var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + var owned = aria.getOwnedVirtual(target); + assert.lengthOf(owned, 2); + assert.equal(owned[0].actualNode.id, 'b'); + assert.equal(owned[1].actualNode.id, 'a'); + }); + + it('moves multiple aria-owned children to the end in aria-owns order', function () { + fixtureSetup( + '
' + + '
A
' + + '
B
' + + '
C
' + + '
' + ); + var target = axe.utils.querySelectorAll(axe._tree[0], '#target')[0]; + var owned = aria.getOwnedVirtual(target); + assert.lengthOf(owned, 3); + assert.equal(owned[0].actualNode.id, 'b'); + assert.equal(owned[1].actualNode.id, 'c'); + assert.equal(owned[2].actualNode.id, 'a'); + }); + it('ignores whitespace-only aria-owned', function () { fixtureSetup( '
' + From a4b809183f43c4296a3ec57cd80d8a8f34743361 Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:15:10 -0700 Subject: [PATCH 16/62] fix(target-size): determine offset using clientRects if target is display:inline (#5012) This pr does two things: 1. For target size we decided to calculate the visible unobscured rects by taking the bounding rect of the target and subtracting the client rects of the obscurer if the obscurer is inline. This is because an inline element only accepts pointer events on the client rects and not the bounding rect. For the target we still take the bounding rect and not the client rects if the target is inline because we both agreed that the goal of the target size rule is to prevent clicking on the wrong target and not that the target itself is large enough to click on. So an inline element with multiple lines would have dead pointer zones within it, but accidentally clicking on a dead zone is not a failure of target size. 2. This also applies using client rects for inline elements for the spacing exception for the same reasons as above. What this does is allow checking multiple target rects for inline nodes and makes sure each rect passes the spacing exception. If at least one rect does not pass the rule fails for that element. Closes: https://github.com/dequelabs/axe-core/issues/4928 --- lib/checks/mobile/target-size-evaluate.js | 21 ++++++--- lib/commons/dom/get-target-rects.js | 20 ++++++--- lib/commons/math/get-offset.js | 44 +++++++++---------- lib/commons/math/split-rects.js | 4 +- test/checks/mobile/target-offset.js | 39 ++++++++++++++++ test/checks/mobile/target-size.js | 30 +++++++++++++ test/commons/dom/get-target-rects.js | 10 +++++ test/commons/math/split-rects.js | 16 +++++++ .../rules/target-size/target-size.html | 21 +++++++++ .../rules/target-size/target-size.json | 7 +++ 10 files changed, 176 insertions(+), 36 deletions(-) diff --git a/lib/checks/mobile/target-size-evaluate.js b/lib/checks/mobile/target-size-evaluate.js index 1fb76e775..a87935d54 100644 --- a/lib/checks/mobile/target-size-evaluate.js +++ b/lib/checks/mobile/target-size-evaluate.js @@ -66,7 +66,11 @@ export default function targetSizeEvaluate(node, options, vNode) { return true; } - const largestInnerRect = getLargestUnobscuredArea(vNode, obscuredWidgets); + const largestInnerRect = getLargestUnobscuredArea( + vNode, + obscuredWidgets, + minSize + ); if (!largestInnerRect) { this.data({ minSize, messageKey: 'tooManyRects' }); return undefined; @@ -127,11 +131,16 @@ function filterByElmsOverlap(vNode, nearbyElms) { } // Find areas of the target that are not obscured -function getLargestUnobscuredArea(vNode, obscuredNodes) { +function getLargestUnobscuredArea(vNode, obscuredNodes, minSize) { const nodeRect = vNode.boundingClientRect; - const obscuringRects = obscuredNodes.map( - ({ boundingClientRect: rect }) => rect - ); + const obscuringRects = obscuredNodes + .map(obscuredNode => { + const display = obscuredNode.getComputedStylePropertyValue('display'); + return display === 'inline' + ? obscuredNode.clientRects + : obscuredNode.boundingClientRect; + }) + .flat(Infinity); let unobscuredRects; try { unobscuredRects = splitRects(nodeRect, obscuringRects); @@ -140,7 +149,7 @@ function getLargestUnobscuredArea(vNode, obscuredNodes) { } // Of the unobscured inner rects, work out the largest - return getLargestRect(unobscuredRects); + return getLargestRect(unobscuredRects, minSize); } // Find the largest rectangle in the array, prioritize ones that meet a minimum size diff --git a/lib/commons/dom/get-target-rects.js b/lib/commons/dom/get-target-rects.js index a2bc2eec1..ebebb2c7f 100644 --- a/lib/commons/dom/get-target-rects.js +++ b/lib/commons/dom/get-target-rects.js @@ -13,7 +13,9 @@ export default memoize(getTargetRects); * @return {DOMRect[]} */ function getTargetRects(vNode) { - const nodeRect = vNode.boundingClientRect; + const display = vNode.getComputedStylePropertyValue('display'); + const nodeRects = + display === 'inline' ? vNode.clientRects : [vNode.boundingClientRect]; const overlappingVNodes = findNearbyElms(vNode).filter(vNeighbor => { return ( hasVisualOverlap(vNode, vNeighbor) && @@ -23,13 +25,19 @@ function getTargetRects(vNode) { }); if (!overlappingVNodes.length) { - return [nodeRect]; + return nodeRects; } - const obscuringRects = overlappingVNodes.map( - ({ boundingClientRect: rect }) => rect - ); - return splitRects(nodeRect, obscuringRects); + const obscuringRects = overlappingVNodes + .map(overlappingVNode => { + const overlappingDisplay = + overlappingVNode.getComputedStylePropertyValue('display'); + return overlappingDisplay === 'inline' + ? overlappingVNode.clientRects + : overlappingVNode.boundingClientRect; + }) + .flat(Infinity); + return splitRects(nodeRects, obscuringRects); } function isDescendantNotInTabOrder(vAncestor, vNode) { diff --git a/lib/commons/math/get-offset.js b/lib/commons/math/get-offset.js index 957d4e43c..40f0e9529 100644 --- a/lib/commons/math/get-offset.js +++ b/lib/commons/math/get-offset.js @@ -21,33 +21,33 @@ export default function getOffset(vTarget, vNeighbor, minRadiusNeighbour = 12) { return null; } - const targetBoundingBox = targetRects.reduce(getBoundingRect); - const targetCenter = getRectCenter(targetBoundingBox); + // calculate distance of each target rect to the closest edge of each neighbor rect + return targetRects.reduce((minDistance, targetRect) => { + const targetCenter = getRectCenter(targetRect); - // calculate distance to the closest edge of each neighbor rect - let minDistance = Infinity; - for (const rect of neighborRects) { - if (isPointInRect(targetCenter, rect)) { - return 0; - } + for (const rect of neighborRects) { + if (isPointInRect(targetCenter, rect)) { + return 0; + } - const closestPoint = getClosestPoint(targetCenter, rect); - const distance = pointDistance(targetCenter, closestPoint); - minDistance = Math.min(minDistance, distance); - } + const closestPoint = getClosestPoint(targetCenter, rect); + const distance = pointDistance(targetCenter, closestPoint); + minDistance = Math.min(minDistance, distance); + } - const neighborTargetSize = getTargetSize(vNeighbor); - if (rectHasMinimumSize(minRadiusNeighbour * 2, neighborTargetSize)) { - return minDistance; - } + const neighborTargetSize = getTargetSize(vNeighbor); + if (rectHasMinimumSize(minRadiusNeighbour * 2, neighborTargetSize)) { + return minDistance; + } - const neighborBoundingBox = neighborRects.reduce(getBoundingRect); - const neighborCenter = getRectCenter(neighborBoundingBox); - // subtract the radius of the circle from the distance to center to get distance to edge of the neighbor circle - const centerDistance = - pointDistance(targetCenter, neighborCenter) - minRadiusNeighbour; + const neighborBoundingBox = neighborRects.reduce(getBoundingRect); + const neighborCenter = getRectCenter(neighborBoundingBox); + // subtract the radius of the circle from the distance to center to get distance to edge of the neighbor circle + const centerDistance = + pointDistance(targetCenter, neighborCenter) - minRadiusNeighbour; - return Math.max(0, Math.min(minDistance, centerDistance)); + return Math.max(0, Math.min(minDistance, centerDistance)); + }, Infinity); } function getClosestPoint(point, rect) { diff --git a/lib/commons/math/split-rects.js b/lib/commons/math/split-rects.js index ea6ba5397..aeb651e57 100644 --- a/lib/commons/math/split-rects.js +++ b/lib/commons/math/split-rects.js @@ -3,12 +3,12 @@ * space that does not overlap. * @method getOffset * @memberof axe.commons.math - * @param {DOMRect} outerRect + * @param {DOMRect|DOMRect[]} outerRect * @param {DOMRect[]} overlapRects * @returns {DOMRect[]} Unique array of rects */ export default function splitRects(outerRect, overlapRects) { - let uniqueRects = [outerRect]; + let uniqueRects = Array.isArray(outerRect) ? outerRect : [outerRect]; for (const overlapRect of overlapRects) { uniqueRects = uniqueRects.reduce((rects, inputRect) => { return rects.concat(splitRect(inputRect, overlapRect)); diff --git a/test/checks/mobile/target-offset.js b/test/checks/mobile/target-offset.js index 747d9a4fa..942690f3c 100644 --- a/test/checks/mobile/target-offset.js +++ b/test/checks/mobile/target-offset.js @@ -34,6 +34,19 @@ describe('target-offset tests', () => { assert.closeTo(checkContext._data.closestOffset, 24, 0.2); }); + it('returns true when wrapped inline elements offset is 24px', () => { + const checkArgs = checkSetup(` + + `); + + assert.isTrue(checkEvaluate.apply(checkContext, checkArgs)); + assert.equal(checkContext._data.minOffset, 24); + assert.closeTo(checkContext._data.closestOffset, 24, 0.2); + }); + describe('when the offset is insufficient', () => { it('returns false for targets in the tab order', () => { const checkArgs = checkSetup( @@ -66,6 +79,32 @@ describe('target-offset tests', () => { assert.equal(checkContext._data.minOffset, 24); assert.closeTo(checkContext._data.closestOffset, 22, 0.2); }); + + it('returns false when wrapped inline elements offset is <24px', () => { + const checkArgs = checkSetup(` + + `); + + assert.isFalse(checkEvaluate.apply(checkContext, checkArgs)); + assert.equal(checkContext._data.minOffset, 24); + assert.closeTo(checkContext._data.closestOffset, 20, 3); + }); + + it('returns false when one line of a wrapped inline elements offset is <24px', () => { + const checkArgs = checkSetup(` + + `); + + assert.isFalse(checkEvaluate.apply(checkContext, checkArgs)); + assert.equal(checkContext._data.minOffset, 24); + assert.closeTo(checkContext._data.closestOffset, 15.5, 5); + }); }); it('ignores non-widget elements as neighbors', () => { diff --git a/test/checks/mobile/target-size.js b/test/checks/mobile/target-size.js index f0769c3c5..bebd01703 100644 --- a/test/checks/mobile/target-size.js +++ b/test/checks/mobile/target-size.js @@ -176,6 +176,36 @@ describe('target-size tests', () => { assert.deepEqual(elmIds(checkContext._relatedNodes), ['#obscurer']); }); + it('returns true for obscured target when an unobscured rect has sufficient size but smaller area', () => { + const checkArgs = checkSetup(` +
+ + +
+ `); + assert.isTrue(check.evaluate.apply(checkContext, checkArgs)); + assert.deepEqual(checkContext._data, { + minSize: 24, + width: 30, + height: 40 + }); + assert.deepEqual(elmIds(checkContext._relatedNodes), ['#obscurer']); + }); + + it('returns true for multiline inline target with sufficient space', () => { + const checkArgs = checkSetup(` + + `); + assert.isTrue(check.evaluate.apply(checkContext, checkArgs)); + assert.closeTo(checkContext._data.width, 40.5, 10); + assert.closeTo(checkContext._data.height, 40.5, 5); + assert.deepEqual(elmIds(checkContext._relatedNodes), ['#obscurer']); + }); + it('returns undefined if there are too many focusable widgets', () => { let html = ''; for (let i = 0; i < 100; i++) { diff --git a/test/commons/dom/get-target-rects.js b/test/commons/dom/get-target-rects.js index be37b7cbb..6f3a7373a 100644 --- a/test/commons/dom/get-target-rects.js +++ b/test/commons/dom/get-target-rects.js @@ -96,4 +96,14 @@ describe('get-target-rects', () => { const rects = getTargetRects(vNode); assert.deepEqual(rects, [vNode.actualNode.getBoundingClientRect()]); }); + + it('uses client rects if target is inline', () => { + const vNode = queryFixture(` + + `); + const rects = getTargetRects(vNode); + assert.deepEqual(rects, Array.from(vNode.actualNode.getClientRects())); + }); }); diff --git a/test/commons/math/split-rects.js b/test/commons/math/split-rects.js index 968b8c7b7..c0579d095 100644 --- a/test/commons/math/split-rects.js +++ b/test/commons/math/split-rects.js @@ -28,6 +28,14 @@ describe('splitRects', () => { }, 'splitRects: Too many rects'); }); + it('accepts an array of rects', () => { + const rectA = new DOMRect(0, 0, 100, 50); + const rectB = new DOMRect(0, 50, 50, 50); + const rects = splitRects([rectA], [rectB]); + assert.lengthOf(rects, 1); + assert.deepEqual(rects[0], rectA); + }); + describe('with one overlapping rect', () => { it('returns one rect if overlaps covers two corners', () => { const rectA = new DOMRect(0, 0, 100, 50); @@ -73,6 +81,14 @@ describe('splitRects', () => { const rects = splitRects(rectA, [rectB]); assert.lengthOf(rects, 0); }); + + it('accepts an array of rects', () => { + const rectA = new DOMRect(0, 0, 100, 50); + const rectB = new DOMRect(40, 0, 100, 50); + const rects = splitRects([rectA], [rectB]); + assert.lengthOf(rects, 1); + assert.deepEqual(rects[0], new DOMRect(0, 0, 40, 50)); + }); }); describe('with multiple overlaps', () => { diff --git a/test/integration/rules/target-size/target-size.html b/test/integration/rules/target-size/target-size.html index 7a2d02180..c57bd9e7b 100644 --- a/test/integration/rules/target-size/target-size.html +++ b/test/integration/rules/target-size/target-size.html @@ -145,6 +145,27 @@ >

+ + + + +
+ + +
+

Date: Wed, 4 Mar 2026 09:10:18 -0500 Subject: [PATCH 17/62] fix(existing-rule): aria-busy now shows an error message for a use with unallowed children (#5017) Updated existing rule for aria-required-children for cases where the aria-busy attribute is used. This now will show the user a specific error message regarding aria-busy with unallowed children instead of the one for used for other cases using unallowed children. Replaces PR: [5011](https://github.com/dequelabs/axe-core/pull/5011) Closes: https://github.com/dequelabs/axe-core/issues/4626 --- lib/checks/aria/aria-required-children-evaluate.js | 5 ++++- lib/checks/aria/aria-required-children.json | 3 ++- locales/_template.json | 3 ++- test/checks/aria/required-children.js | 4 ++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/checks/aria/aria-required-children-evaluate.js b/lib/checks/aria/aria-required-children-evaluate.js index 1e7512d96..403cd4979 100644 --- a/lib/checks/aria/aria-required-children-evaluate.js +++ b/lib/checks/aria/aria-required-children-evaluate.js @@ -41,8 +41,11 @@ export default function ariaRequiredChildrenEvaluate( if (unallowed.length) { this.relatedNodes(unallowed.map(({ vNode }) => vNode)); + const messageKey = + virtualNode.attr('aria-busy') === 'true' ? 'aria-busy-fail' : 'unallowed'; + this.data({ - messageKey: 'unallowed', + messageKey, values: unallowed .map(({ vNode, attr }) => getUnallowedSelector(vNode, attr)) .filter((selector, index, array) => array.indexOf(selector) === index) diff --git a/lib/checks/aria/aria-required-children.json b/lib/checks/aria/aria-required-children.json index 16c67dbd5..016f56b5e 100644 --- a/lib/checks/aria/aria-required-children.json +++ b/lib/checks/aria/aria-required-children.json @@ -27,7 +27,8 @@ "fail": { "singular": "Required ARIA child role not present: ${data.values}", "plural": "Required ARIA children role not present: ${data.values}", - "unallowed": "Element has children which are not allowed: ${data.values}" + "unallowed": "Element has children which are not allowed: ${data.values}", + "aria-busy-fail": "Element has children which are not allowed: ${data.values}; Having aria-busy=\"true\" does not allow children with roles that are not allowed" }, "incomplete": { "singular": "Expecting ARIA child role to be added: ${data.values}", diff --git a/locales/_template.json b/locales/_template.json index 673574914..3a2144901 100644 --- a/locales/_template.json +++ b/locales/_template.json @@ -513,7 +513,8 @@ "fail": { "singular": "Required ARIA child role not present: ${data.values}", "plural": "Required ARIA children role not present: ${data.values}", - "unallowed": "Element has children which are not allowed: ${data.values}" + "unallowed": "Element has children which are not allowed: ${data.values}", + "aria-busy-fail": "Element has children which are not allowed: ${data.values}; Having aria-busy=\"true\" does not allow children with roles that are not allowed" }, "incomplete": { "singular": "Expecting ARIA child role to be added: ${data.values}", diff --git a/test/checks/aria/required-children.js b/test/checks/aria/required-children.js index faf68eb71..8cfab3a17 100644 --- a/test/checks/aria/required-children.js +++ b/test/checks/aria/required-children.js @@ -146,14 +146,14 @@ describe('aria-required-children', () => { }); }); - it('should fail list with an unallowed child, even if aria-busy="true"', () => { + it('should fail list with an unallowed child but show a custom message, even if aria-busy="true"', () => { const params = checkSetup(`

`); assert.isFalse(requiredChildrenCheck.apply(checkContext, params)); assert.deepEqual(checkContext._data, { - messageKey: 'unallowed', + messageKey: 'aria-busy-fail', values: '[role=tabpanel]' }); }); From 88c367694ab8d6f78453cebda74c63dbcd91e326 Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Sat, 7 Mar 2026 04:50:57 -0700 Subject: [PATCH 18/62] test: fix chromedriver 146 failing to create session (#5026) Similar to our fix in [axe-core-npm](https://github.com/dequelabs/axe-core-npm/pull/1291) this pins chrome/driver to v145 in order to fix the failing tests due to chromedriver crashing or failing to create a session. It also fixes the memory issue in https://github.com/dequelabs/axe-core/issues/5018 and adds some chrome options that are good for ci usage. I also created a tech debt ticket to track the pin. See https://github.com/dequelabs/axe-core/actions/runs/22737133182?pr=5026 that it shows 10 successful runs. Closes: #501 --- .github/actions/install-deps/action.yml | 3 ++- test/act-rules/act-runner.js | 2 +- test/aria-practices/apg.spec.js | 2 +- test/get-webdriver.js | 7 ++++++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/actions/install-deps/action.yml b/.github/actions/install-deps/action.yml index b7d220b7f..40c9aa43d 100644 --- a/.github/actions/install-deps/action.yml +++ b/.github/actions/install-deps/action.yml @@ -56,7 +56,8 @@ runs: id: setup-chrome uses: browser-actions/setup-chrome@b94431e051d1c52dcbe9a7092a4f10f827795416 # v2.1.0 with: - chrome-version: ${{ inputs.nightly == 'true' && 'beta' || 'stable' }} + # @see https://github.com/dequelabs/axe-core/issues/5027 + chrome-version: ${{ inputs.nightly == 'true' && 'beta' || 145 }} install-chromedriver: true install-dependencies: true - name: Install Firefox diff --git a/test/act-rules/act-runner.js b/test/act-rules/act-runner.js index 3d3eab094..ae3a10fd6 100644 --- a/test/act-rules/act-runner.js +++ b/test/act-rules/act-runner.js @@ -47,7 +47,7 @@ module.exports = ({ id, title, axeRules, skipTests = [] }) => { }); after(async () => { - await driver.close(); + await driver.quit(); await new Promise(r => server.close(r)); }); diff --git a/test/aria-practices/apg.spec.js b/test/aria-practices/apg.spec.js index e03a4cc06..aaebe6f14 100644 --- a/test/aria-practices/apg.spec.js +++ b/test/aria-practices/apg.spec.js @@ -28,7 +28,7 @@ describe('aria-practices', function () { }); after(async () => { - await driver.close(); + await driver.quit(); }); const disabledRules = { diff --git a/test/get-webdriver.js b/test/get-webdriver.js index c71a55710..ce38fdaa4 100644 --- a/test/get-webdriver.js +++ b/test/get-webdriver.js @@ -5,7 +5,12 @@ const chromedriverPath = const getWebdriver = () => { const service = new chrome.ServiceBuilder(chromedriverPath); - const options = new chrome.Options().addArguments('--headless'); + const options = new chrome.Options().addArguments( + '--headless', + '--no-sandbox', + '--disable-dev-shm-usage', + '--disable-gpu' + ); if (process.env.CHROME_BIN) { options.setBinaryPath(process.env.CHROME_BIN); From 133e57332ef5f947cf985bfbf59925de95086e21 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 20:39:39 -0400 Subject: [PATCH 19/62] chore: bump jsdom from 27.4.0 to 28.1.0 (#5021) Bumps [jsdom](https://github.com/jsdom/jsdom) from 27.4.0 to 28.1.0.
Release notes

Sourced from jsdom's releases.

Version 28.1.0

  • Added blob.text(), blob.arrayBuffer(), and blob.bytes() methods.
  • Improved getComputedStyle() to account for CSS specificity when multiple rules apply. (asamuzaK)
  • Improved synchronous XMLHttpRequest performance by using a persistent worker thread, avoiding ~400ms of setup overhead on every synchronous request after the first one.
  • Improved performance of node.getRootNode(), node.isConnected, and event.dispatchEvent() by caching the root node of document-connected trees.
  • Fixed getComputedStyle() to correctly handle !important priority. (asamuzaK)
  • Fixed document.getElementById() to return the first element in tree order when multiple elements share the same ID.
  • Fixed <svg> elements to no longer incorrectly proxy event handlers to the Window.
  • Fixed FileReader event timing and fileReader.result state to more closely follow the spec.
  • Fixed a potential hang when synchronous XMLHttpRequest encountered dispatch errors.
  • Fixed compatibility with environments where Node.js's built-in fetch() has been used before importing jsdom, by working around undici v6/v7 incompatibilities.

Version 28.0.0

  • Overhauled resource loading customization. See the new README for details on the new API.
  • Added MIME type sniffing to <iframe> and <frame> loads.
  • Regression: WebSockets are no longer correctly throttled to one connection per origin. This is a result of the bug at nodejs/undici#4743.
  • Fixed decoding of the query components of <a> and <area> elements in non-UTF-8 documents.
  • Fixed XMLHttpRequest fetches and WebSocket upgrade requests to be interceptable by the new customizable resource loading. (Except synchronous XMLHttpRequests.)
  • Fixed the referrer of a document to be set correctly when redirects are involved; it is now the initiating page, not the last hop in the redirect chain.
  • Fixed correctness bugs when passing ArrayBuffers or typed arrays to various APIs, where they would not correctly snapshot the data.
  • Fixed require("url").parse() deprecation warning when using WebSockets.
  • Fixed <iframe>, <frame>, and <img> (when canvas is installed) to fire load events, not error events, on non-OK HTTP responses.
  • Fixed many small issues in XMLHttpRequest.
Changelog

Sourced from jsdom's changelog.

28.1.0

  • Added blob.text(), blob.arrayBuffer(), and blob.bytes() methods.
  • Improved getComputedStyle() to account for CSS specificity when multiple rules apply. (asamuzaK)
  • Improved synchronous XMLHttpRequest performance by using a persistent worker thread, avoiding ~400ms of setup overhead on every synchronous request after the first one.
  • Improved performance of node.getRootNode(), node.isConnected, and event.dispatchEvent() by caching the root node of document-connected trees.
  • Fixed getComputedStyle() to correctly handle !important priority. (asamuzaK)
  • Fixed document.getElementById() to return the first element in tree order when multiple elements share the same ID.
  • Fixed <svg> elements to no longer incorrectly proxy event handlers to the Window.
  • Fixed FileReader event timing and fileReader.result state to more closely follow the spec.
  • Fixed a potential hang when synchronous XMLHttpRequest encountered dispatch errors.
  • Fixed compatibility with environments where Node.js's built-in fetch() has been used before importing jsdom, by working around undici v6/v7 incompatibilities.

28.0.0

  • Overhauled resource loading customization. See the new README for details on the new API.
  • Added MIME type sniffing to <iframe> and <frame> loads.
  • Regression: WebSockets are no longer correctly throttled to one connection per origin. This is a result of the bug at nodejs/undici#4743.
  • Fixed decoding of the query components of <a> and <area> elements in non-UTF-8 documents.
  • Fixed XMLHttpRequest fetches and WebSocket upgrade requests to be interceptable by the new customizable resource loading. (Except synchronous XMLHttpRequests.)
  • Fixed the referrer of a document to be set correctly when redirects are involved; it is now the initiating page, not the last hop in the redirect chain.
  • Fixed correctness bugs when passing ArrayBuffers or typed arrays to various APIs, where they would not correctly snapshot the data.
  • Fixed require("url").parse() deprecation warning when using WebSockets.
  • Fixed <iframe>, <frame>, and <img> (when canvas is installed) to fire load events, not error events, on non-OK HTTP responses.
  • Fixed many small issues in XMLHttpRequest.
Commits
  • 12949b5 Version 28.1.0
  • ce4c58f Apply CSS specificity when computing styles
  • 7ed55a0 Skip single-byte-decoder encoding tests on Node 20
  • f3b1973 Generalize node version conditions in test expectations
  • 853c596 Rewrite getElementById ID caching for tree-order correctness
  • 5fbfde6 Fix potential sync XHR worker hang from unhandled dispatch errors
  • 82df38f Cache the root node for document-connected trees
  • ed7c5c0 Add documentation comment to create-event-accessor.js
  • b4562e9 Simplify Window.js installEventHandlers
  • 7da340f Centralize "determine the target of an event handler"
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=jsdom&package-manager=npm_and_yarn&previous-version=27.4.0&new-version=28.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 384 +++++++++++++++++++++++++--------------------- package.json | 2 +- 2 files changed, 213 insertions(+), 173 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6e7580ca7..593201387 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "inquirer": "^8.2.5", "jquery": "^4.0.0", "jsdoc": "^4.0.2", - "jsdom": "^27.0.0", + "jsdom": "^28.1.0", "karma": "^6.4.1", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^3.1.1", @@ -93,50 +93,53 @@ } }, "node_modules/@acemir/cssom": { - "version": "0.9.30", - "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.30.tgz", - "integrity": "sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==", + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", "dev": true }, "node_modules/@asamuzakjp/css-color": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.1.tgz", - "integrity": "sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", + "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", "dev": true, "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "lru-cache": "^11.2.4" + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true, "engines": { "node": "20 || >=22" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "6.7.6", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.6.tgz", - "integrity": "sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", "dev": true, "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.4" + "lru-cache": "^11.2.6" } }, "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true, "engines": { "node": "20 || >=22" @@ -1701,6 +1704,18 @@ "integrity": "sha512-RzahvqTkfpY2jsDxo8YItPX+/iZ6hbiikw1YhE0bA9EKBR5Og8Pa6FHn9PO9M0zaXRVsr0GFQLKbB/0rzy9SzA==", "dev": true }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -1711,9 +1726,9 @@ } }, "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", "dev": true, "funding": [ { @@ -1726,13 +1741,13 @@ } ], "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", "dev": true, "funding": [ { @@ -1745,17 +1760,17 @@ } ], "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", "dev": true, "funding": [ { @@ -1768,21 +1783,21 @@ } ], "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "dev": true, "funding": [ { @@ -1795,16 +1810,16 @@ } ], "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.22.tgz", - "integrity": "sha512-qBcx6zYlhleiFfdtzkRgwNC7VVoAwfK76Vmsw5t+PbvtdknO9StgRk7ROvq9so1iqbdW4uLIDAsXRsTfUrIoOw==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.28.tgz", + "integrity": "sha512-1NRf1CUBjnr3K7hu8BLxjQrKCxEe8FP/xmPTenAxCRZWVLbmGotkFvG9mfNpjA6k7Bw1bw4BilZq9cu19RA5pg==", "dev": true, "funding": [ { @@ -1815,15 +1830,12 @@ "type": "opencollective", "url": "https://opencollective.com/csstools" } - ], - "engines": { - "node": ">=18" - } + ] }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "dev": true, "funding": [ { @@ -1836,7 +1848,7 @@ } ], "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@deque/dot": { @@ -4347,24 +4359,24 @@ } }, "node_modules/cssstyle": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.6.tgz", - "integrity": "sha512-legscpSpgSAeGEe0TNcai97DKt9Vd9AsAdOL7Uoetb52Ar/8eJm3LIa39qpv8wWzLFlNG4vVvppQM+teaMPj3A==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.1.0.tgz", + "integrity": "sha512-Ml4fP2UT2K3CUBQnVlbdV/8aFDdlY69E+YnwJM+3VUWl08S3J8c8aRuJqCkD9Py8DHZ7zNNvsfKl8psocHZEFg==", "dev": true, "dependencies": { - "@asamuzakjp/css-color": "^4.1.1", - "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "@asamuzakjp/css-color": "^5.0.0", + "@csstools/css-syntax-patches-for-csstree": "^1.0.28", "css-tree": "^3.1.0", - "lru-cache": "^11.2.4" + "lru-cache": "^11.2.6" }, "engines": { "node": ">=20" } }, "node_modules/cssstyle/node_modules/lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true, "engines": { "node": "20 || >=22" @@ -4399,16 +4411,16 @@ } }, "node_modules/data-urls": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", - "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "dev": true, "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/date-format": { @@ -7974,16 +7986,17 @@ } }, "node_modules/jsdom": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz", - "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", + "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", "dev": true, "dependencies": { - "@acemir/cssom": "^0.9.28", - "@asamuzakjp/dom-selector": "^6.7.6", - "@exodus/bytes": "^1.6.0", - "cssstyle": "^5.3.4", - "data-urls": "^6.0.0", + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.8.1", + "@bramus/specificity": "^2.4.2", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^6.0.1", + "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", @@ -7993,11 +8006,11 @@ "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", + "undici": "^7.21.0", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.0", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.1.0", - "ws": "^8.18.3", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", "xml-name-validator": "^5.0.0" }, "engines": { @@ -12619,6 +12632,15 @@ "node": "*" } }, + "node_modules/undici": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "dev": true, + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", @@ -12876,9 +12898,9 @@ } }, "node_modules/webidl-conversions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", - "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true, "engines": { "node": ">=20" @@ -12932,25 +12954,26 @@ } }, "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", "dev": true, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/whatwg-url": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", - "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", "dev": true, "dependencies": { + "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", - "webidl-conversions": "^8.0.0" + "webidl-conversions": "^8.0.1" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/which": { @@ -13308,49 +13331,49 @@ "dev": true }, "@acemir/cssom": { - "version": "0.9.30", - "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.30.tgz", - "integrity": "sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==", + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", "dev": true }, "@asamuzakjp/css-color": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.1.tgz", - "integrity": "sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", + "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", "dev": true, "requires": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "lru-cache": "^11.2.4" + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.6" }, "dependencies": { "lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true } } }, "@asamuzakjp/dom-selector": { - "version": "6.7.6", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.6.tgz", - "integrity": "sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", "dev": true, "requires": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.4" + "lru-cache": "^11.2.6" }, "dependencies": { "lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true } } @@ -14395,6 +14418,15 @@ "integrity": "sha512-RzahvqTkfpY2jsDxo8YItPX+/iZ6hbiikw1YhE0bA9EKBR5Og8Pa6FHn9PO9M0zaXRVsr0GFQLKbB/0rzy9SzA==", "dev": true }, + "@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "requires": { + "css-tree": "^3.0.0" + } + }, "@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -14402,45 +14434,45 @@ "dev": true }, "@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", "dev": true }, "@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", "dev": true, "requires": {} }, "@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", "dev": true, "requires": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" } }, "@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "dev": true, "requires": {} }, "@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.22.tgz", - "integrity": "sha512-qBcx6zYlhleiFfdtzkRgwNC7VVoAwfK76Vmsw5t+PbvtdknO9StgRk7ROvq9so1iqbdW4uLIDAsXRsTfUrIoOw==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.28.tgz", + "integrity": "sha512-1NRf1CUBjnr3K7hu8BLxjQrKCxEe8FP/xmPTenAxCRZWVLbmGotkFvG9mfNpjA6k7Bw1bw4BilZq9cu19RA5pg==", "dev": true }, "@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "dev": true }, "@deque/dot": { @@ -16370,21 +16402,21 @@ "dev": true }, "cssstyle": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.6.tgz", - "integrity": "sha512-legscpSpgSAeGEe0TNcai97DKt9Vd9AsAdOL7Uoetb52Ar/8eJm3LIa39qpv8wWzLFlNG4vVvppQM+teaMPj3A==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.1.0.tgz", + "integrity": "sha512-Ml4fP2UT2K3CUBQnVlbdV/8aFDdlY69E+YnwJM+3VUWl08S3J8c8aRuJqCkD9Py8DHZ7zNNvsfKl8psocHZEFg==", "dev": true, "requires": { - "@asamuzakjp/css-color": "^4.1.1", - "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "@asamuzakjp/css-color": "^5.0.0", + "@csstools/css-syntax-patches-for-csstree": "^1.0.28", "css-tree": "^3.1.0", - "lru-cache": "^11.2.4" + "lru-cache": "^11.2.6" }, "dependencies": { "lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true } } @@ -16412,13 +16444,13 @@ "dev": true }, "data-urls": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", - "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "dev": true, "requires": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" } }, "date-format": { @@ -19043,16 +19075,17 @@ } }, "jsdom": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz", - "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", + "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", "dev": true, "requires": { - "@acemir/cssom": "^0.9.28", - "@asamuzakjp/dom-selector": "^6.7.6", - "@exodus/bytes": "^1.6.0", - "cssstyle": "^5.3.4", - "data-urls": "^6.0.0", + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.8.1", + "@bramus/specificity": "^2.4.2", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^6.0.1", + "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", @@ -19062,11 +19095,11 @@ "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", + "undici": "^7.21.0", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.0", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.1.0", - "ws": "^8.18.3", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", "xml-name-validator": "^5.0.0" }, "dependencies": { @@ -22557,6 +22590,12 @@ "util-deprecate": "^1.0.2" } }, + "undici": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "dev": true + }, "undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", @@ -22742,9 +22781,9 @@ "dev": true }, "webidl-conversions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", - "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true }, "websocket-driver": { @@ -22785,19 +22824,20 @@ } }, "whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", "dev": true }, "whatwg-url": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", - "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", "dev": true, "requires": { + "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", - "webidl-conversions": "^8.0.0" + "webidl-conversions": "^8.0.1" } }, "which": { diff --git a/package.json b/package.json index c5b7c3fb0..655745309 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,7 @@ "inquirer": "^8.2.5", "jquery": "^4.0.0", "jsdoc": "^4.0.2", - "jsdom": "^27.0.0", + "jsdom": "^28.1.0", "karma": "^6.4.1", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^3.1.1", From 5c1a55cb52379edaedd174d7ef9f191c2ce6e1e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 21:23:50 -0400 Subject: [PATCH 20/62] chore: bump the npm-low-risk group with 6 updates (#5020) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the npm-low-risk group with 6 updates: | Package | From | To | | --- | --- | --- | | [@axe-core/webdriverjs](https://github.com/dequelabs/axe-core-npm) | `4.11.0` | `4.11.1` | | [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) | `7.28.6` | `7.29.0` | | [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) | `7.28.6` | `7.29.0` | | [@babel/runtime-corejs3](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime-corejs3) | `7.28.6` | `7.29.0` | | [globals](https://github.com/sindresorhus/globals) | `17.2.0` | `17.3.0` | | [selenium-webdriver](https://github.com/SeleniumHQ/selenium) | `4.40.0` | `4.41.0` | Updates `@axe-core/webdriverjs` from 4.11.0 to 4.11.1
Release notes

Sourced from @​axe-core/webdriverjs's releases.

Release 4.11.1

Bug Fixes

Changelog

Sourced from @​axe-core/webdriverjs's changelog.

4.11.1 (2026-01-09)

Bug Fixes

Commits
Maintainer changes

This version was pushed to npm by [GitHub Actions](https://www.npmjs.com/~GitHub Actions), a new releaser for @​axe-core/webdriverjs since your current version.


Updates `@babel/core` from 7.28.6 to 7.29.0
Release notes

Sourced from @​babel/core's releases.

v7.29.0 (2026-01-31)

Thanks @​simbahax for your first PR!

:rocket: New Feature

  • babel-types
  • babel-standalone

:bug: Bug Fix

  • babel-parser
  • babel-traverse
    • #17708 fix(traverse): provide a hub when traversing a File or Program and no parentPath is given (@​simbahax)
  • babel-plugin-transform-block-scoping, babel-traverse
    • #17737 [7.x backport] fix: Rename switch discriminant references when body creates shadowing variable (@​magic-akari)

:running_woman: Performance

Committers: 6

Commits

Updates `@babel/preset-env` from 7.28.6 to 7.29.0
Release notes

Sourced from @​babel/preset-env's releases.

v7.29.0 (2026-01-31)

Thanks @​simbahax for your first PR!

:rocket: New Feature

  • babel-types
  • babel-standalone

:bug: Bug Fix

  • babel-parser
  • babel-traverse
    • #17708 fix(traverse): provide a hub when traversing a File or Program and no parentPath is given (@​simbahax)
  • babel-plugin-transform-block-scoping, babel-traverse
    • #17737 [7.x backport] fix: Rename switch discriminant references when body creates shadowing variable (@​magic-akari)

:running_woman: Performance

Committers: 6

Commits

Updates `@babel/runtime-corejs3` from 7.28.6 to 7.29.0
Release notes

Sourced from @​babel/runtime-corejs3's releases.

v7.29.0 (2026-01-31)

Thanks @​simbahax for your first PR!

:rocket: New Feature

  • babel-types
  • babel-standalone

:bug: Bug Fix

  • babel-parser
  • babel-traverse
    • #17708 fix(traverse): provide a hub when traversing a File or Program and no parentPath is given (@​simbahax)
  • babel-plugin-transform-block-scoping, babel-traverse
    • #17737 [7.x backport] fix: Rename switch discriminant references when body creates shadowing variable (@​magic-akari)

:running_woman: Performance

Committers: 6

Commits

Updates `globals` from 17.2.0 to 17.3.0
Release notes

Sourced from globals's releases.

v17.3.0

  • Update globals (2026-02-01) (#336) 295fba9

https://github.com/sindresorhus/globals/compare/v17.2.0...v17.3.0

Commits

Updates `selenium-webdriver` from 4.40.0 to 4.41.0
Release notes

Sourced from selenium-webdriver's releases.

Selenium 4.41.0

Detailed Changelogs by Component

Java     |     Python     |     DotNet     |     Ruby     |     JavaScript

What's Changed

... (truncated)

Commits
  • 9fc754f [build] Prepare for release of selenium-4.41.0 (#17098)
  • d5f22ec [java] mark tests passing in latest chrome beta
  • 89c5993 [build] fix auto-updating of browsers
  • 4592f1b [build] ci-python jobs not currently matching by default
  • 755d44c [build] put cdp version support in changelogs
  • 9aff5c7 [build] cannot invoke a rake task twice by default
  • 3a680a3 [build] ignore the staging branch for the PR and apply all patches in order
  • 20de9b9 [build] stage changes to an ephemeral staging branch since not all updates ar...
  • aac9a28 [py] Update test to check it's an integer rather than a value (#17114)
  • 02ec15f [rb] Update dependencies (#17111)
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stephen Mathieson <571265+stephenmathieson@users.noreply.github.com> --- package-lock.json | 449 ++++++++++++++++++++++++---------------------- 1 file changed, 234 insertions(+), 215 deletions(-) diff --git a/package-lock.json b/package-lock.json index 593201387..a5bea7637 100644 --- a/package-lock.json +++ b/package-lock.json @@ -152,12 +152,12 @@ "dev": true }, "node_modules/@axe-core/webdriverjs": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@axe-core/webdriverjs/-/webdriverjs-4.11.0.tgz", - "integrity": "sha512-Qt9DWcCQIPU9TMfCQnibFn0JDuvoSzfGs4nZM7LJeae/i3bpZ5qqLjc94U09BUneYQjTicfwHlKTcr9mmAb2cg==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@axe-core/webdriverjs/-/webdriverjs-4.11.1.tgz", + "integrity": "sha512-UVq7uBsqaQlHj7PQPg+WhE0nV6ccYtE0cnGglStS5RE3RAB+gQcb30R1k3usI+Q/xyROSJHhusOus4BzqrlZ1w==", "dev": true, "dependencies": { - "axe-core": "~4.11.0" + "axe-core": "~4.11.1" }, "peerDependencies": { "selenium-webdriver": ">3.0.0-beta || >=2.53.1 || >4.0.0-alpha" @@ -187,20 +187,20 @@ } }, "node_modules/@babel/core": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", - "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -314,25 +314,25 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", - "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", + "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "debug": "^4.4.1", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", "lodash.debounce": "^4.0.8", - "resolve": "^1.22.10" + "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -1123,15 +1123,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", - "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.5" + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -1158,14 +1158,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1544,12 +1543,12 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.6.tgz", - "integrity": "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", + "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.28.6", + "@babel/compat-data": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", @@ -1563,7 +1562,7 @@ "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.6", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.6", @@ -1574,7 +1573,7 @@ "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.6", "@babel/plugin-transform-exponentiation-operator": "^7.28.6", @@ -1587,9 +1586,9 @@ "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@babel/plugin-transform-modules-systemjs": "^7.28.5", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-numeric-separator": "^7.28.6", @@ -1601,7 +1600,7 @@ "@babel/plugin-transform-private-methods": "^7.28.6", "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.6", + "@babel/plugin-transform-regenerator": "^7.29.0", "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", @@ -1614,10 +1613,10 @@ "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "engines": { @@ -1642,12 +1641,12 @@ } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.6.tgz", - "integrity": "sha512-kz2fAQ5UzjV7X7D3ySxmj3vRq89dTpqOZWv76Z6pNPztkwb/0Yj1Mtx1xFrYj6mbIHysxtBot8J4o0JLCblcFw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.0.tgz", + "integrity": "sha512-TgUkdp71C9pIbBcHudc+gXZnihEDOjUAmXO1VO4HHGES7QLZcShR0stfKIxLSNIYx2fqhmJChOjm/wkF8wv4gA==", "dev": true, "dependencies": { - "core-js-pure": "^3.43.0" + "core-js-pure": "^3.48.0" }, "engines": { "node": ">=6.9.0" @@ -2786,9 +2785,9 @@ "dev": true }, "node_modules/axe-core": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz", - "integrity": "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", + "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==", "dev": true, "engines": { "node": ">=4" @@ -2806,13 +2805,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", - "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", + "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.27.7", - "@babel/helper-define-polyfill-provider": "^0.6.5", + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.6", "semver": "^6.3.1" }, "peerDependencies": { @@ -2820,25 +2819,25 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz", + "integrity": "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" + "@babel/helper-define-polyfill-provider": "^0.6.6", + "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", - "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", + "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5" + "@babel/helper-define-polyfill-provider": "^0.6.6" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -2879,6 +2878,18 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -3066,9 +3077,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -3085,10 +3096,11 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -3233,9 +3245,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001731", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz", - "integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==", + "version": "1.0.30001775", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001775.tgz", + "integrity": "sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A==", "dev": true, "funding": [ { @@ -4246,12 +4258,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.44.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.44.0.tgz", - "integrity": "sha512-JepmAj2zfl6ogy34qfWtcE7nHKAJnKsQFRn++scjVS2bZFllwptzw61BZcZFYBPpUznLfAvh0LGhxKppk04ClA==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", "dev": true, "dependencies": { - "browserslist": "^4.25.1" + "browserslist": "^4.28.1" }, "funding": { "type": "opencollective", @@ -4259,9 +4271,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.44.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.44.0.tgz", - "integrity": "sha512-gvMQAGB4dfVUxpYD0k3Fq8J+n5bB6Ytl15lqlZrOIXFzxOhtPaObfkQGHtMRdyjIf7z2IeNULwi1jEwyS+ltKQ==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.48.0.tgz", + "integrity": "sha512-1slJgk89tWC51HQ1AEqG+s2VuwpTRr8ocu4n20QUcH1v9lAN0RXen0Q0AABa/DK1I7RrNWLucplOHMx8hfTGTw==", "dev": true, "hasInstallScript": true, "funding": { @@ -4875,9 +4887,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.5.194", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.194.tgz", - "integrity": "sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA==", + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", "dev": true }, "node_modules/emoji-regex": { @@ -6377,9 +6389,9 @@ } }, "node_modules/globals": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.2.0.tgz", - "integrity": "sha512-tovnCz/fEq+Ripoq+p/gN1u7l6A7wwkoBT9pRCzTHzsD/LvADIzXZdjmRymh5Ztf0DYC3Rwg5cZRYjxzBmzbWg==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", + "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", "dev": true, "engines": { "node": ">=18" @@ -9629,9 +9641,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true }, "node_modules/nopt": { @@ -11157,12 +11169,12 @@ } }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -11332,9 +11344,9 @@ "dev": true }, "node_modules/selenium-webdriver": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.40.0.tgz", - "integrity": "sha512-dU0QbnVKdPmoNP8OtMCazRdtU2Ux6Wl4FEpG1iwUbDeajJK1dBAywBLrC1D7YFRtogHzN96AbXBgBAJaarcysw==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.41.0.tgz", + "integrity": "sha512-1XxuKVhr9az24xwixPBEDGSZP+P0z3ZOnCmr9Oiep0MlJN2Mk+flIjD3iBS9BgyjS4g14dikMqnrYUPIjhQBhA==", "dev": true, "funding": [ { @@ -11350,7 +11362,7 @@ "@bazel/runfiles": "^6.5.0", "jszip": "^3.10.1", "tmp": "^0.2.5", - "ws": "^8.18.3" + "ws": "^8.19.0" }, "engines": { "node": ">= 20.0.0" @@ -12724,9 +12736,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -13119,9 +13131,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "dev": true, "engines": { "node": ">=10.0.0" @@ -13385,12 +13397,12 @@ "dev": true }, "@axe-core/webdriverjs": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@axe-core/webdriverjs/-/webdriverjs-4.11.0.tgz", - "integrity": "sha512-Qt9DWcCQIPU9TMfCQnibFn0JDuvoSzfGs4nZM7LJeae/i3bpZ5qqLjc94U09BUneYQjTicfwHlKTcr9mmAb2cg==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@axe-core/webdriverjs/-/webdriverjs-4.11.1.tgz", + "integrity": "sha512-UVq7uBsqaQlHj7PQPg+WhE0nV6ccYtE0cnGglStS5RE3RAB+gQcb30R1k3usI+Q/xyROSJHhusOus4BzqrlZ1w==", "dev": true, "requires": { - "axe-core": "~4.11.0" + "axe-core": "~4.11.1" } }, "@babel/code-frame": { @@ -13411,20 +13423,20 @@ "dev": true }, "@babel/core": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", - "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "requires": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -13512,22 +13524,22 @@ } }, "@babel/helper-define-polyfill-provider": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", - "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", + "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", "dev": true, "requires": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "debug": "^4.4.1", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", "lodash.debounce": "^4.0.8", - "resolve": "^1.22.10" + "resolve": "^1.22.11" }, "dependencies": { "debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "requires": { "ms": "^2.1.3" @@ -14026,15 +14038,15 @@ } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", - "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.5" + "@babel/traverse": "^7.29.0" } }, "@babel/plugin-transform-modules-umd": { @@ -14048,13 +14060,13 @@ } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-new-target": { @@ -14279,12 +14291,12 @@ } }, "@babel/preset-env": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.6.tgz", - "integrity": "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", + "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", "dev": true, "requires": { - "@babel/compat-data": "^7.28.6", + "@babel/compat-data": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", @@ -14298,7 +14310,7 @@ "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.6", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.6", @@ -14309,7 +14321,7 @@ "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.6", "@babel/plugin-transform-exponentiation-operator": "^7.28.6", @@ -14322,9 +14334,9 @@ "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@babel/plugin-transform-modules-systemjs": "^7.28.5", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-numeric-separator": "^7.28.6", @@ -14336,7 +14348,7 @@ "@babel/plugin-transform-private-methods": "^7.28.6", "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.6", + "@babel/plugin-transform-regenerator": "^7.29.0", "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", @@ -14349,10 +14361,10 @@ "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", "semver": "^6.3.1" } }, @@ -14368,12 +14380,12 @@ } }, "@babel/runtime-corejs3": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.6.tgz", - "integrity": "sha512-kz2fAQ5UzjV7X7D3ySxmj3vRq89dTpqOZWv76Z6pNPztkwb/0Yj1Mtx1xFrYj6mbIHysxtBot8J4o0JLCblcFw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.0.tgz", + "integrity": "sha512-TgUkdp71C9pIbBcHudc+gXZnihEDOjUAmXO1VO4HHGES7QLZcShR0stfKIxLSNIYx2fqhmJChOjm/wkF8wv4gA==", "dev": true, "requires": { - "core-js-pure": "^3.43.0" + "core-js-pure": "^3.48.0" } }, "@babel/template": { @@ -15214,9 +15226,9 @@ "dev": true }, "axe-core": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz", - "integrity": "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", + "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==", "dev": true }, "axios": { @@ -15231,33 +15243,33 @@ } }, "babel-plugin-polyfill-corejs2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", - "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", + "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", "dev": true, "requires": { - "@babel/compat-data": "^7.27.7", - "@babel/helper-define-polyfill-provider": "^0.6.5", + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.6", "semver": "^6.3.1" } }, "babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz", + "integrity": "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" + "@babel/helper-define-polyfill-provider": "^0.6.6", + "core-js-compat": "^3.48.0" } }, "babel-plugin-polyfill-regenerator": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", - "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", + "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.6.5" + "@babel/helper-define-polyfill-provider": "^0.6.6" } }, "balanced-match": { @@ -15278,6 +15290,12 @@ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "dev": true }, + "baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true + }, "basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -15449,15 +15467,16 @@ "dev": true }, "browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" } }, "buffer": { @@ -15554,9 +15573,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001731", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz", - "integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==", + "version": "1.0.30001775", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001775.tgz", + "integrity": "sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A==", "dev": true }, "catharsis": { @@ -16319,18 +16338,18 @@ "dev": true }, "core-js-compat": { - "version": "3.44.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.44.0.tgz", - "integrity": "sha512-JepmAj2zfl6ogy34qfWtcE7nHKAJnKsQFRn++scjVS2bZFllwptzw61BZcZFYBPpUznLfAvh0LGhxKppk04ClA==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", "dev": true, "requires": { - "browserslist": "^4.25.1" + "browserslist": "^4.28.1" } }, "core-js-pure": { - "version": "3.44.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.44.0.tgz", - "integrity": "sha512-gvMQAGB4dfVUxpYD0k3Fq8J+n5bB6Ytl15lqlZrOIXFzxOhtPaObfkQGHtMRdyjIf7z2IeNULwi1jEwyS+ltKQ==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.48.0.tgz", + "integrity": "sha512-1slJgk89tWC51HQ1AEqG+s2VuwpTRr8ocu4n20QUcH1v9lAN0RXen0Q0AABa/DK1I7RrNWLucplOHMx8hfTGTw==", "dev": true }, "core-util-is": { @@ -16787,9 +16806,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.5.194", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.194.tgz", - "integrity": "sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA==", + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", "dev": true }, "emoji-regex": { @@ -17914,9 +17933,9 @@ } }, "globals": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.2.0.tgz", - "integrity": "sha512-tovnCz/fEq+Ripoq+p/gN1u7l6A7wwkoBT9pRCzTHzsD/LvADIzXZdjmRymh5Ztf0DYC3Rwg5cZRYjxzBmzbWg==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", + "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", "dev": true }, "globule": { @@ -20308,9 +20327,9 @@ } }, "node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true }, "nopt": { @@ -21469,12 +21488,12 @@ } }, "resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "requires": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } @@ -21602,15 +21621,15 @@ "dev": true }, "selenium-webdriver": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.40.0.tgz", - "integrity": "sha512-dU0QbnVKdPmoNP8OtMCazRdtU2Ux6Wl4FEpG1iwUbDeajJK1dBAywBLrC1D7YFRtogHzN96AbXBgBAJaarcysw==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.41.0.tgz", + "integrity": "sha512-1XxuKVhr9az24xwixPBEDGSZP+P0z3ZOnCmr9Oiep0MlJN2Mk+flIjD3iBS9BgyjS4g14dikMqnrYUPIjhQBhA==", "dev": true, "requires": { "@bazel/runfiles": "^6.5.0", "jszip": "^3.10.1", "tmp": "^0.2.5", - "ws": "^8.18.3" + "ws": "^8.19.0" } }, "semver": { @@ -22658,9 +22677,9 @@ "dev": true }, "update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "requires": { "escalade": "^3.2.0", @@ -22953,9 +22972,9 @@ "dev": true }, "ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "dev": true, "requires": {} }, From 3af1fb77a5382af188b69004e90b0658c43d62ba Mon Sep 17 00:00:00 2001 From: Dylan Barrell Date: Thu, 19 Mar 2026 16:14:44 -0400 Subject: [PATCH 21/62] chore: add CLAUDE.md and pull request checklist (#5035) Adds a CLAUDE.md file for contributors --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jonathan Garbee --- CLAUDE.md | 102 ++++++++++++++++++ doc/examples/code-patterns.md | 152 +++++++++++++++++++++++++++ doc/examples/pr-review-patterns.md | 69 ++++++++++++ doc/examples/rule-check-templates.md | 112 ++++++++++++++++++++ doc/examples/test-patterns.md | 94 +++++++++++++++++ doc/pull-request-checklist.md | 42 ++++++++ 6 files changed, 571 insertions(+) create mode 100644 CLAUDE.md create mode 100644 doc/examples/code-patterns.md create mode 100644 doc/examples/pr-review-patterns.md create mode 100644 doc/examples/rule-check-templates.md create mode 100644 doc/examples/test-patterns.md create mode 100644 doc/pull-request-checklist.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..8a81ac332 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,102 @@ +# axe-core — Claude Code Context + +**Last updated:** 2026-03-19 + +## 0. Fundamental Standards + +- **Formatting:** Run `npm run fmt` (Prettier) and `npm run eslint` before every commit. No exceptions. +- **Zero-Exception Testing:** 100% coverage goal. Run `npm test` before completion. `feat`/`fix` and other behavior-changing PRs require unit + integration tests where applicable; see `doc/code-submission-guidelines.md` for exceptions (e.g., some `chore`/docs changes). +- **Import Rule:** Directory-level import restrictions are strictly enforced. See `eslint.config.js`. +- **Commits:** Angular commit convention is mandatory. PRs with non-conforming commits will be rejected. See `doc/code-submission-guidelines.md`. +- **Issues:** All unresolved issues tracked in [GitHub Issues](https://github.com/dequelabs/axe-core/issues). + +## 1. Technical Guidelines + +### Code Structure + +- **Return Early:** Keep the happy path left-aligned. Handle errors/edge cases first with early returns — never nest when you can exit. +- **Exports:** Default export at the top of the file, immediately after imports. +- **JSDoc:** Add JSDoc/DocBlock comments where appropriate, especially for exported APIs and complex logic. Document parameters and return types. See `doc/code-submission-guidelines.md` (source of truth) and `doc/developer-guide.md` for additional guidance. +- **Naming:** Files and rule/check IDs in kebab-case. Functions in camelCase. Booleans prefixed `is`/`has`/`should`. Constants in `UPPER_SNAKE_CASE`. +- **Variables:** Declare at point of use, not at the top of the function. + +### Virtual Nodes vs. HTMLElement + +- **Prefer Virtual Nodes** for attribute access and property reads (`virtualNode.attr()`, `virtualNode.props`). +- **Use real DOM** only when you need DOM APIs (e.g., `getBoundingClientRect`, `getRootNode`). +- **`core/utils/` functions** should default to real DOM inputs/outputs, except utilities that are explicitly documented as operating on `VirtualNode` (for example, tree or selector helpers). Avoid introducing new `VirtualNode`-dependent utilities unless there is a clear performance or API benefit. +- **Conversion:** Use `getNodeFromTree()` from `core/utils` when you receive an ambiguous input (such as a `VirtualNode`, selector, or mixed type) and need to resolve it to a real DOM `Node`. + +### Import Restrictions (Hard Rules) + +- `standards/` → nothing (pure data, no imports). +- `core/utils/` → other `core/utils`, `core/base`, `standards` via **direct file paths only** (no index). +- `core/imports/` → node modules **only** (the only place npm imports are allowed). +- `commons/` → other `commons` (direct paths), `core/utils` (index OK). +- `checks/` and `rules/` → any directory (index OK). +- **Never** import `commons` from `core/utils`. This is the most commonly rejected violation. + +### Checks & Rules + +- **Check evaluate functions:** Return `true` (pass), `false` (fail), or `undefined` (incomplete). Use `this.data()` to pass values to message templates and to provide incomplete-result detail. +- **Rule JSON:** Use `all`, `any`, `none` check arrays. `selector` + optional `matches` to scope candidates. Valid `impact` values: `"minor"`, `"moderate"`, `"serious"`, `"critical"`. +- **Standards data:** Never hardcode ARIA/HTML lists in checks. Query from `standards/` via `commons/standards` functions. +- **Messages:** All user-facing strings live in check/rule JSON `metadata.messages`. Use `${data.property}` templates. Support singular/plural variants. Update `locales/_template.json` whenever messages change (auto-synced on `npm run build`). + +### High-Risk Areas (Extra Scrutiny Required) + +- **Color contrast:** Handles all CSS color formats, opacity, blend modes, stacking contexts, text-shadow. Return `undefined` when background cannot be determined. See `doc/developer-guide.md`. +- **ARIA validation:** Must stay current with spec. Query roles/attrs from `standards/aria-roles.js` and `standards/aria-attrs.js`. Handle implicit vs. explicit roles. See `doc/rule-development.md`. +- **Hidden elements:** Use `isVisibleToScreenReaders()`, not CSS visibility alone. Account for `aria-hidden="true"` and Shadow DOM boundaries. +- **i18n:** Update `locales/_template.json` on every message change and commit the generated file alongside source. + +## 2. Testing + +- **Structure:** Mirror `lib/` exactly under `test/`. File `lib/commons/text/sanitize.js` → `test/commons/text/sanitize.js`. +- **Checks:** Use `axe.testUtils.MockCheckContext()`. Only reset `checkContext` in `afterEach` — `fixture` and `axe._tree` are auto-cleared. +- **Integration tests:** All rule changes require an HTML + JSON pair. Use `test/integration/rules//` for mocha-hosted tests or `test/integration/full//` when the rule requires a full HTML page. JSON selectors must use axe array format (`["#id"]`; iframes: `["iframe", "#id"]`). Also update or create virtual-rules tests where appropriate. +- **Shadow DOM:** Every relevant check/rule must include an open Shadow DOM test case using `queryShadowFixture`. +- **Logging:** Do not commit `console.log` statements. + +## 3. Build & Commits + +- **Bundles (`axe.js`, `axe.min.js`)** are auto-generated for releases/publishing and are not committed to the repo (they are gitignored). +- **Locales template (`locales/_template.json`)** is auto-generated. When message strings change, regenerate this file and commit it in the same commit as the source changes — never in a separate commit. +- **One change per PR.** Do not mix refactoring with feature work. +- **Commit format:** `(): ` — imperative, lowercase, no period, ≤100 chars total. Body explains motivation. Footer: `Closes issue #123` or full URL. See `doc/code-submission-guidelines.md` for the full type list. + +**Example:** + +``` +fix(aria-valid-attr-value): handle multiple aria-errormessage IDs + +When aria-errormessage contains multiple space-separated IDs, verify +all IDs exist in aria-describedby instead of matching the full string. + +Closes issue #4957 +``` + +## 4. Documentation & API Changes + +- **New rules:** Update `CHANGELOG.md`. `doc/rule-descriptions.md` is auto-generated by `npm run build`. +- **API changes:** Update `doc/API.md` and `axe.d.ts` TypeScript definitions. +- **Breaking changes:** Avoid breaking changes — prefer supporting both old and new formats simultaneously. If unavoidable, add `BREAKING CHANGE: description` to commit footer, include migration guide in `CHANGELOG.md`, and tag deprecated code with `@deprecated` JSDoc. + +## 5. Examples (Copy-Paste Reference) + +- **Code patterns:** `doc/examples/code-patterns.md` — return early, default export, imports, JSDoc, Virtual Node usage +- **Test patterns:** `doc/examples/test-patterns.md` — unit tests, check tests, Shadow DOM tests, integration test HTML+JSON +- **Rule & check templates:** `doc/examples/rule-check-templates.md` — JSON templates for rules and checks, evaluate function pattern +- **PR review patterns:** `doc/examples/pr-review-patterns.md` — common reviewer feedback, anti-patterns, what reviewers look for + +## 6. Reference Docs & Help + +- **Contributing guide:** `CONTRIBUTING.md` +- **Import rules detail:** `eslint.config.js` +- **Code submission standards:** `doc/code-submission-guidelines.md` +- **Developer guide:** `doc/developer-guide.md` +- **Rule development:** `doc/rule-development.md` +- **API reference:** `doc/API.md` +- **Pull Request Checklist:** `doc/pull-request-checklist.md` +- **Slack:** [axe-community](https://accessibility.deque.com/axe-community) +- **Issues:** [GitHub Issues](https://github.com/dequelabs/axe-core/issues) diff --git a/doc/examples/code-patterns.md b/doc/examples/code-patterns.md new file mode 100644 index 000000000..e342b8f1e --- /dev/null +++ b/doc/examples/code-patterns.md @@ -0,0 +1,152 @@ +# Code Pattern Examples + +Quick-reference examples for axe-core coding conventions. + +## Default Export at Top + +```javascript +// GOOD: Default export right after imports +import { getRole } from '../../commons/aria'; +import { isVisible } from '../../commons/dom'; + +export default function myFunction(node, options) { + // function body +} + +// BAD: Export buried at bottom of file +import { getRole } from '../../commons/aria'; + +function myFunction(node, options) { + // body +} + +// ... more code ... + +export default myFunction; // Too far from top +``` + +## Return Early Pattern + +```javascript +// GOOD: Main path left-aligned, edge cases exit early +export default function processValue(value) { + if (!value) { + return null; + } + + if (value.length < 3) { + throw new Error('Value too short'); + } + + const normalized = normalize(value); + const result = transform(normalized); + return result; +} + +// BAD: Nested conditionals +export default function processValue(value) { + let result; + if (value) { + if (value.length >= 3) { + const normalized = normalize(value); + result = transform(normalized); + } else { + throw new Error('Value too short'); + } + } else { + result = null; + } + return result; +} +``` + +## Import Restrictions + +```javascript +// GOOD: commons importing from core/utils via index +import { getNodeFromTree } from '../../core/utils'; + +// GOOD: commons importing other commons directly +import getExplicitRole from '../aria/get-explicit-role'; + +// BAD: core/utils importing from commons — NEVER DO THIS +import { isDisabled } from '../../commons/forms'; + +// BAD: importing from index in core/utils — use direct path +import { someUtil } from './index'; // Use: import someUtil from './some-util'; +``` + +## JSDoc Comments + +### Standard function + +```javascript +/** + * Determines if an element is a native select element + * @method isNativeSelect + * @memberof axe.commons.forms + * @param {VirtualNode|Element} node Node to determine if select + * @returns {Boolean} + */ +import nodeLookup from '../../core/utils/node-lookup'; + +function isNativeSelect(node) { + const { vNode } = nodeLookup(node); + const nodeName = vNode.props.nodeName; + return nodeName === 'select'; +} +``` + +### Check evaluate function + +```javascript +/** + * Check if an element's `role` attribute uses any abstract role values. + * + * Abstract roles are taken from the `ariaRoles` standards object from the roles `type` property. + * + * ##### Data: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
TypeDescription
String[]List of all abstract roles
+ * + * @memberof checks + * @return {Boolean} True if the element uses an `abstract` role. False otherwise. + */ +function abstractroleEvaluate(node, options, virtualNode) { + // implementation +} +``` + +## Virtual Node vs HTMLElement + +```javascript +// Use Virtual Node for attribute access and property reads +function myCheck(node, options, virtualNode) { + const role = virtualNode.attr('role'); // Cached attribute access + const nodeName = virtualNode.props.nodeName; + + // Only access real node when you need DOM APIs + const rect = node.getBoundingClientRect(); + const rootNode = node.getRootNode(); +} + +// Convert ambiguous input using nodeLookup +import nodeLookup from '../../core/utils/node-lookup'; + +function myFunction(nodeOrVirtual) { + const { vNode, domNode } = nodeLookup(nodeOrVirtual); + // vNode = VirtualNode, domNode = real DOM node +} +``` diff --git a/doc/examples/pr-review-patterns.md b/doc/examples/pr-review-patterns.md new file mode 100644 index 000000000..a56324054 --- /dev/null +++ b/doc/examples/pr-review-patterns.md @@ -0,0 +1,69 @@ +# PR Review Patterns + +Common feedback and anti-patterns observed in axe-core code reviews. + +## What Gets Called Out + +### 1. Missing Tests + +- Every behavior-changing code change needs unit tests +- Rule changes need integration tests (HTML + JSON pair) +- Shadow DOM test coverage required for relevant checks/rules + +### 2. Commit Message Format + +- Wrong type/scope +- Not imperative present tense +- Subject too long or capitalized +- Missing issue reference in footer + +### 3. Import Violations + +- `core/utils` importing from `commons` (forbidden) +- Using index imports where direct file paths are required +- Importing node modules outside `core/imports` + +### 4. Code Style Issues + +- Not using return early pattern +- Default export not at top of file +- Nested conditionals when early return would work +- Missing JSDoc comments + +### 5. Performance Concerns + +- Unnecessary DOM queries in loops +- Not caching Virtual Node properties +- Computing same value repeatedly instead of storing it + +### 6. Incomplete Results + +- Not returning `undefined` when a check can't determine the result +- Missing `incomplete` message variants in check JSON +- Not setting appropriate `this.data()` for incomplete cases + +### 7. Accessibility Edge Cases + +- Not considering all ARIA states +- Missing edge cases for hidden elements +- Not handling Shadow DOM properly + +## What Reviewers Love + +1. Comprehensive test coverage including edge cases +2. Clear, detailed commit messages with motivation +3. JSDoc comments that explain "why" not just "what" +4. Performance-conscious code that caches appropriately +5. Following existing patterns in similar files +6. Integration tests that cover both pass and fail cases + +## Common PR Mistakes + +1. Not running `npm test` locally before pushing +2. Not committing auto-generated `locales/_template.json` in the same commit as message source changes +3. Changing multiple unrelated things in one PR — split refactoring from feature work +4. Not updating integration tests when changing rule behavior +5. `console.log` statements should not be committed +6. Not handling `null` or `undefined` gracefully +7. Hardcoding strings that should come from `standards/` data +8. Changing public APIs without `BREAKING CHANGE` in commit footer diff --git a/doc/examples/rule-check-templates.md b/doc/examples/rule-check-templates.md new file mode 100644 index 000000000..5bfd25109 --- /dev/null +++ b/doc/examples/rule-check-templates.md @@ -0,0 +1,112 @@ +# Rule & Check JSON Templates + +Quick-reference templates for creating axe-core rules and checks. + +## Rule JSON + +```json +{ + "id": "aria-input-field-name", + "impact": "serious", + "selector": "[role=\"textbox\"], [role=\"combobox\"]", + "matches": "no-naming-method-matches", + "tags": ["cat.aria", "wcag2a", "wcag412"], + "metadata": { + "description": "Ensure every ARIA input field has an accessible name", + "help": "ARIA input fields must have an accessible name" + }, + "all": [], + "any": ["aria-label", "aria-labelledby", "non-empty-title"], + "none": ["no-implicit-explicit-label"] +} +``` + +**Key properties:** + +| Property | Description | +| ----------- | ------------------------------------------------------------- | +| `selector` | CSS selector for candidate elements | +| `matches` | Optional function to further filter elements | +| `all` | All checks must pass | +| `any` | At least one check must pass | +| `none` | All checks must fail (for the rule to pass) | +| `impact` | `"minor"`, `"moderate"`, `"serious"`, `"critical"` | +| `enabled` | Defaults to `true`; set `false` for disabled/deprecated rules | +| `tags` | Grouping: `wcag2a`, `wcag2aa`, `best-practice`, `cat.*`, etc. | +| `pageLevel` | `true` if rule should only run on the top-level window | + +## Check JSON + +```json +{ + "id": "aria-allowed-attr", + "evaluate": "aria-allowed-attr-evaluate", + "options": { + "validTreeRowAttrs": ["aria-posinset", "aria-setsize"] + }, + "metadata": { + "impact": "critical", + "messages": { + "pass": "ARIA attributes are used correctly for the defined role", + "fail": { + "singular": "ARIA attribute is not allowed: ${data.values}", + "plural": "ARIA attributes are not allowed: ${data.values}" + }, + "incomplete": "Check that there is no problem if the ARIA attribute is ignored: ${data.values}" + } + } +} +``` + +**Message templates:** + +- Use `${data.property}` for dynamic values set via `this.data()` +- Support singular/plural variants via object with `singular`/`plural` keys +- `locales/_template.json` is auto-generated by `npm run build` — do not edit it manually, but commit the regenerated file alongside source changes + +## Check Evaluate Function + +```javascript +export default function myCheckEvaluate(node, options, virtualNode) { + // node: HTMLElement + // options: from check JSON or runtime config + // virtualNode: Virtual Node representation + + // Early returns for edge cases + if (someEdgeCase) { + return true; // Pass + } + + // Compute what you need + const relevantData = computeStuff(virtualNode); + + // Set data for messages (optional) + // this.data() sets an arbitrary object passed to message templates + if (relevantData.hasIssues) { + this.data({ + messageKey: 'someIssue', // Selects which message variant to use (e.g., singular/plural) + values: relevantData.issueList // ${data.values} in templates resolves arrays + }); + } + + // Return boolean or undefined + if (cannotDetermine) { + this.data({ reason: 'could not determine background color' }); + return undefined; // Incomplete + } + + return isValid; // true = pass, false = fail +} +``` + +**Return values:** + +- `true` — check passed +- `false` — check failed +- `undefined` — incomplete (cannot determine result) + +**`this.data()` usage:** + +- Sets an arbitrary data object that gets passed to message templates via `${data.propertyName}` +- `messageKey` — selects which message variant to use (e.g., `singular`/`plural`) +- Any other keys are available in templates as `${data.keyName}` — arrays are automatically joined diff --git a/doc/examples/test-patterns.md b/doc/examples/test-patterns.md new file mode 100644 index 000000000..e373ee0ce --- /dev/null +++ b/doc/examples/test-patterns.md @@ -0,0 +1,94 @@ +# Test Pattern Examples + +Quick-reference examples for axe-core test conventions. + +## Unit Test — Commons Function + +```javascript +describe('text.sanitize', function () { + it('should collapse whitespace and trim', function () { + assert.equal(axe.commons.text.sanitize('\thi\t'), 'hi'); + assert.equal(axe.commons.text.sanitize('\t\nhi \t'), 'hi'); + assert.equal(axe.commons.text.sanitize('hello\u00A0there'), 'hello there'); + }); + + it('should accept null', function () { + assert.equal(axe.commons.text.sanitize(null), ''); + }); +}); +``` + +## Unit Test — Check Evaluate + +```javascript +describe('aria-allowed-attr', function () { + const fixture = document.getElementById('fixture'); + const checkContext = axe.testUtils.MockCheckContext(); + + afterEach(function () { + checkContext.reset(); + }); + + it('should return true if all ARIA attributes are allowed', function () { + const vNode = queryFixture( + '
' + ); + + assert.isTrue( + axe.testUtils + .getCheckEvaluate('aria-allowed-attr') + .call(checkContext, vNode.actualNode, {}, vNode) + ); + }); +}); +``` + +## Shadow DOM Test + +```javascript +it('should work with Shadow DOM', function () { + const vNode = queryShadowFixture( + '
', + '
Test
' + ); + // Test your function against the shadow DOM content +}); +``` + +## Integration Test — Rule + +Each rule change requires an HTML + JSON pair in `test/integration/rules//` (mocha-hosted) or `test/integration/full//` (full HTML page). Virtual-rules tests in `test/virtual-rules/` should also be updated or created for appropriate rules. See sections below for details. + +### HTML file (`aria-allowed-attr.html`) + +```html +
Valid
+
Invalid
+``` + +### JSON file (`aria-allowed-attr.json`) + +```json +{ + "description": "aria-allowed-attr test", + "rule": "aria-allowed-attr", + "violations": [["#fail1"]], + "passes": [["#pass1"]] +} +``` + +IDs use axe selector array format. For elements inside iframes: + +```json +{ + "violations": [["iframe", "#fail-inside-iframe"]] +} +``` + +### Full-page integration tests + +For rules that need a complete HTML page (e.g., landmark rules, page-level rules), use `test/integration/full/` instead. These tests run against a full HTML document rather than injected fragments. + +### Virtual rules tests + +Rules that can run without a DOM (using only virtual nodes) should have tests in `test/virtual-rules/`. These tests verify that rules work correctly with `axe.run()` on serialized node data. diff --git a/doc/pull-request-checklist.md b/doc/pull-request-checklist.md new file mode 100644 index 000000000..c36a6e05d --- /dev/null +++ b/doc/pull-request-checklist.md @@ -0,0 +1,42 @@ +# axe-core — PR Checklist + +Complete all applicable items before opening a PR. For items that do not apply to this PR (for example, tests for a `chore`-only change), mark them as "N/A" and follow `doc/code-submission-guidelines.md`. Reviewers will not merge until all required boxes are checked. + +## Code + +- [ ] Default export is at the top of the file, immediately after imports +- [ ] Return early pattern used — no nested conditionals where an early return works +- [ ] DocBlock comments where appropriate (especially for public/exported APIs), per `doc/code-submission-guidelines.md` +- [ ] No `console.log` statements committed +- [ ] No hardcoded ARIA/HTML lists — queried from `standards/` via `commons/standards` +- [ ] Imports follow directory restrictions — especially no `commons` from `core/utils` + +## Tests + +- [ ] Unit tests cover all code paths (`null`/`undefined` input handling only needed for public API functions) +- [ ] Integration test HTML + JSON pair added/updated for any rule changes — use `integration/rules` for mocha-hosted tests or `integration/full` when the rule requires a full HTML page +- [ ] Virtual-rules tests updated or created for appropriate rules +- [ ] Shadow DOM test case added where relevant +- [ ] Code built with `npm run build` before testing +- [ ] All applicable tests pass locally: `npm test` (unit), `test:integration`, `test:virtual-rules`, `test:act`, `test:apg`, `test:examples`, `test:node`, `test:jsdom` + +## Formatting & Build + +- [ ] `npm run fmt` passes (Prettier) +- [ ] `npm run eslint` passes +- [ ] `npm run build` run — generated artifacts that are tracked in git (for example `locales/_template.json`) are committed in the same commit as source changes + +## Commits + +- [ ] All commits follow Angular format: `(): ` +- [ ] Subject is imperative, lowercase, no trailing period, ≤100 chars total +- [ ] Commit body explains motivation (not just what changed) +- [ ] Footer includes issue reference: `Closes issue: #123` or full URL +- [ ] Breaking changes avoided — prefer supporting both old and new formats simultaneously. If unavoidable, document in footer: `BREAKING CHANGE: description` + +## Docs + +- [ ] `doc/rule-descriptions.md` is auto-generated by `npm run build` — verify it regenerates correctly for new rules +- [ ] `doc/API.md` and `axe.d.ts` updated for API changes +- [ ] `locales/_template.json` updated if messages changed +- [ ] `CHANGELOG.md` updated with migration guide for breaking changes From a8dd81be759c670203784acf7b1894257df5457c Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:06:12 -0600 Subject: [PATCH 22/62] fix(target-size): ignore widgets that are inline with other inline elements (#5000) Closes: https://github.com/dequelabs/axe-core/issues/4392 --------- Co-authored-by: Wilco Fiers Co-authored-by: Wilco Fiers --- lib/commons/dom/is-in-text-block.js | 87 +++-- lib/rules/widget-not-inline-matches.js | 3 +- test/commons/dom/is-in-text-block.js | 351 +++++++++++++----- .../rules/target-size/target-size.html | 30 +- .../rules/target-size/target-size.json | 1 + 5 files changed, 337 insertions(+), 135 deletions(-) diff --git a/lib/commons/dom/is-in-text-block.js b/lib/commons/dom/is-in-text-block.js index 3f4a4f39b..ecf1f6a77 100644 --- a/lib/commons/dom/is-in-text-block.js +++ b/lib/commons/dom/is-in-text-block.js @@ -1,37 +1,11 @@ import getComposedParent from './get-composed-parent'; import sanitize from '../text/sanitize'; -import { getNodeFromTree } from '../../core/utils'; +import { getNodeFromTree, nodeLookup } from '../../core/utils'; import getRoleType from '../aria/get-role-type'; -function walkDomNode(node, functor) { - if (functor(node.actualNode) !== false) { - node.children.forEach(child => walkDomNode(child, functor)); - } -} +const blockLike = ['block', 'list-item', 'table', 'flex', 'grid']; -const blockLike = [ - 'block', - 'list-item', - 'table', - 'flex', - 'grid', - 'inline-block' -]; - -function isBlock(elm) { - const display = window.getComputedStyle(elm).getPropertyValue('display'); - return blockLike.includes(display) || display.substr(0, 6) === 'table-'; -} - -function getBlockParent(node) { - // Find the closest parent - let parentBlock = getComposedParent(node); - while (parentBlock && !isBlock(parentBlock)) { - parentBlock = getComposedParent(parentBlock); - } - - return getNodeFromTree(parentBlock); -} +const inlineBlockLike = ['inline-block', 'inline-flex', 'inline-grid']; /** * Determines if an element is within a text block @@ -41,16 +15,21 @@ function getBlockParent(node) { * @param {Element} node [description] * @param {Object} options Optional * @property {Bool} noLengthCompare + * @property {Bool} includeInlineBlock * @return {Boolean} [description] */ -function isInTextBlock(node, options) { - if (isBlock(node)) { - // Ignore if the link is a block +function isInTextBlock( + node, + { noLengthCompare, includeInlineBlock = false } = {} +) { + const { vNode, domNode } = nodeLookup(node); + if (isBlock(domNode) || (!includeInlineBlock && isInlineBlockLike(vNode))) { + // Ignore if the element is a block return false; } // Find all the text part of the parent block not in a link, and all the text in a link - const virtualParent = getBlockParent(node); + const virtualParent = getBlockParent(domNode); let parentText = ''; let widgetText = ''; let inBrBlock = 0; @@ -58,6 +37,10 @@ function isInTextBlock(node, options) { // We want to ignore hidden text, and if br / hr is used, only use the section of the parent // that has the link we're looking at walkDomNode(virtualParent, currNode => { + if (currNode === virtualParent.actualNode) { + return true; // Skip the element itself + } + // We're already passed it, skip everything else if (inBrBlock === 2) { return false; @@ -73,11 +56,12 @@ function isInTextBlock(node, options) { } const nodeName = (currNode.nodeName || '').toUpperCase(); - if (currNode === node) { + if (currNode === domNode) { inBrBlock = 1; } + const nodeIsBlock = isBlock(currNode); // BR and HR elements break the line - if (['BR', 'HR'].includes(nodeName)) { + if (nodeIsBlock || ['BR', 'HR'].includes(nodeName)) { if (inBrBlock === 0) { parentText = ''; widgetText = ''; @@ -86,7 +70,9 @@ function isInTextBlock(node, options) { } // Don't walk nodes with content not displayed on screen. - } else if ( + } + if ( + nodeIsBlock || currNode.style.display === 'none' || currNode.style.overflow === 'hidden' || !['', null, 'none'].includes(currNode.style.float) || @@ -103,7 +89,7 @@ function isInTextBlock(node, options) { }); parentText = sanitize(parentText); - if (options?.noLengthCompare) { + if (noLengthCompare) { return parentText.length !== 0; } @@ -112,3 +98,30 @@ function isInTextBlock(node, options) { } export default isInTextBlock; + +function isBlock(node) { + const { vNode } = nodeLookup(node); + const display = vNode.getComputedStylePropertyValue('display'); + return blockLike.includes(display) || display.substr(0, 6) === 'table-'; +} + +function isInlineBlockLike(vNode) { + const display = vNode.getComputedStylePropertyValue('display'); + return inlineBlockLike.includes(display); +} + +function walkDomNode(node, functor) { + if (functor(node.actualNode) !== false) { + node.children.forEach(child => walkDomNode(child, functor)); + } +} + +function getBlockParent(node) { + // Find the closest parent + let parentBlock = getComposedParent(node); + while (parentBlock && !isBlock(parentBlock)) { + parentBlock = getComposedParent(parentBlock); + } + + return getNodeFromTree(parentBlock); +} diff --git a/lib/rules/widget-not-inline-matches.js b/lib/rules/widget-not-inline-matches.js index 61e5f2270..14b1036ef 100644 --- a/lib/rules/widget-not-inline-matches.js +++ b/lib/rules/widget-not-inline-matches.js @@ -14,7 +14,8 @@ const matchesFns = [ (node, vNode) => isFocusable(vNode), // Skip nested widgets with tabindex=-1 (node, vNode) => isInTabOrder(vNode) || !hasWidgetAncestorInTabOrder(vNode), - node => !isInTextBlock(node, { noLengthCompare: true }) + node => + !isInTextBlock(node, { noLengthCompare: true, includeInlineBlock: true }) ]; function isWidgetType(vNode) { diff --git a/test/commons/dom/is-in-text-block.js b/test/commons/dom/is-in-text-block.js index 936669371..4693ad386 100644 --- a/test/commons/dom/is-in-text-block.js +++ b/test/commons/dom/is-in-text-block.js @@ -1,138 +1,132 @@ -describe('dom.isInTextBlock', function () { - 'use strict'; +describe('dom.isInTextBlock', () => { + const fixture = document.getElementById('fixture'); + const { shadowSupport, fixtureSetup, queryFixture } = axe.testUtils; + const isInTextBlock = axe.commons.dom.isInTextBlock; - var fixture = document.getElementById('fixture'); - var shadowSupport = axe.testUtils.shadowSupport; - var fixtureSetup = axe.testUtils.fixtureSetup; - - afterEach(function () { - fixture.innerHTML = ''; - }); - - it('returns true if the element is a node in a block of text', function () { + it('returns true if the element is a node in a block of text', () => { fixtureSetup( '

Some paragraph with text ' + ' link' + '

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

Some paragraph with text ' + ' link' + '

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

link

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

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

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

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

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

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

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

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

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

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

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

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

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

Some paragraph with text
' + ' link' + '

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

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

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

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

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

' + ' link' + @@ -143,81 +137,188 @@ describe('dom.isInTextBlock', function () { ' ' + '

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

' + ' link' + '

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

Some paragraph with text

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

Some paragraph with text ' + - ' link

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

Some paragraph with text ' + + ' link

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

amy link text is longer

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

' + ' link 1\t\n\r' + @@ -226,12 +327,74 @@ describe('dom.isInTextBlock', function () { ' link 4' + '

' ); - var link = document.getElementById('link'); - assert.isFalse( - axe.commons.dom.isInTextBlock(link, { - noLengthCompare: true - }) - ); + const link = document.getElementById('link'); + assert.isFalse(isInTextBlock(link, { noLengthCompare: true })); + }); + }); + + describe('with options.includeInlineBlock: true', () => { + it('returns true if inline-block element has text sibling', () => { + const target = queryFixture(` +
+ Hello world + +
+ `); + assert.isTrue(isInTextBlock(target, { includeInlineBlock: true })); + }); + + it('returns true if inline-block element has inline element sibling', () => { + const target = queryFixture(` +
+ Hello world + +
+ `); + assert.isTrue(isInTextBlock(target, { includeInlineBlock: true })); + }); + + it('returns true if inline-block element has text sibling after it', () => { + const target = queryFixture(` +
+ hello world +
+ `); + assert.isTrue(isInTextBlock(target, { includeInlineBlock: true })); + }); + + it('returns true if inline-block element has both inline text and a widget sibling', () => { + const target = queryFixture(` +
+ + Hello world, goodbye mars + +
+ `); + assert.isTrue(isInTextBlock(target, { includeInlineBlock: true })); + }); + + it('returns false the inline text sibling is on a different line', () => { + const target = queryFixture(` +
+ Hello +
+ +
+ world +
+ `); + assert.isFalse(isInTextBlock(target, { includeInlineBlock: true })); + }); + + it('returns true if inline-block element has a sibling on the same line', () => { + const target = queryFixture(` +
+ Hello +
+ world +
+ `); + assert.isFalse(isInTextBlock(target, { includeInlineBlock: true })); }); }); }); diff --git a/test/integration/rules/target-size/target-size.html b/test/integration/rules/target-size/target-size.html index c57bd9e7b..8f6ff287f 100644 --- a/test/integration/rules/target-size/target-size.html +++ b/test/integration/rules/target-size/target-size.html @@ -68,6 +68,30 @@
Timer
+ +
+

+ This is a in a + sentence. +

+ +
+

- x + + x

    diff --git a/test/integration/rules/target-size/target-size.json b/test/integration/rules/target-size/target-size.json index 5f8d2e148..570d08350 100644 --- a/test/integration/rules/target-size/target-size.json +++ b/test/integration/rules/target-size/target-size.json @@ -36,6 +36,7 @@ ["#pass22"], ["#pass23"], ["#pass24"], + ["#pass25"], ["#pass-adjacent"], ["#pass-fixed"] ], From 3c35b6552474dcfff55b0fb24ecf3b45e6651ded Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:47:45 -0400 Subject: [PATCH 23/62] chore: bump actions/download-artifact from 7.0.0 to 8.0.1 (#5056) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7.0.0 to 8.0.1.
    Release notes

    Sourced from actions/download-artifact's releases.

    v8.0.1

    What's Changed

    Full Changelog: https://github.com/actions/download-artifact/compare/v8...v8.0.1

    v8.0.0

    v8 - What's new

    [!IMPORTANT] actions/download-artifact@v8 has been migrated to an ESM module. This should be transparent to the caller but forks might need to make significant changes.

    [!IMPORTANT] Hash mismatches will now error by default. Users can override this behavior with a setting change (see below).

    Direct downloads

    To support direct uploads in actions/upload-artifact, the action will no longer attempt to unzip all downloaded files. Instead, the action checks the Content-Type header ahead of unzipping and skips non-zipped files. Callers wishing to download a zipped file as-is can also set the new skip-decompress parameter to true.

    Enforced checks (breaking)

    A previous release introduced digest checks on the download. If a download hash didn't match the expected hash from the server, the action would log a warning. Callers can now configure the behavior on mismatch with the digest-mismatch parameter. To be secure by default, we are now defaulting the behavior to error which will fail the workflow run.

    ESM

    To support new versions of the @actions/* packages, we've upgraded the package to ESM.

    What's Changed

    Full Changelog: https://github.com/actions/download-artifact/compare/v7...v8.0.0

    Commits
    • 3e5f45b Add regression tests for CJK characters (#471)
    • e6d03f6 Add a regression test for artifact name + content-type mismatches (#472)
    • 70fc10c Merge pull request #461 from actions/danwkennedy/digest-mismatch-behavior
    • f258da9 Add change docs
    • ccc058e Fix linting issues
    • bd7976b Add a setting to specify what to do on hash mismatch and default it to error
    • ac21fcf Merge pull request #460 from actions/danwkennedy/download-no-unzip
    • 15999bf Add note about package bumps
    • 974686e Bump the version to v8 and add release notes
    • fbe48b1 Update test names to make it clearer what they do
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/download-artifact&package-manager=github_actions&previous-version=7.0.0&new-version=8.0.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7988fcc0c..9c9ec6cf0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -136,7 +136,7 @@ jobs: - *install-deps - &restore-axe-build name: Restore axe build - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: axe-core - name: Run ACT Tests From b9ffe0395b84222375d0820327b3aeeaa9ec948b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:48:28 -0400 Subject: [PATCH 24/62] chore: bump actions/upload-artifact from 6.0.0 to 7.0.0 (#5054) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6.0.0 to 7.0.0.
    Release notes

    Sourced from actions/upload-artifact's releases.

    v7.0.0

    v7 What's new

    Direct Uploads

    Adds support for uploading single files directly (unzipped). Callers can set the new archive parameter to false to skip zipping the file during upload. Right now, we only support single files. The action will fail if the glob passed resolves to multiple files. The name parameter is also ignored with this setting. Instead, the name of the artifact will be the name of the uploaded file.

    ESM

    To support new versions of the @actions/* packages, we've upgraded the package to ESM.

    What's Changed

    New Contributors

    Full Changelog: https://github.com/actions/upload-artifact/compare/v6...v7.0.0

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=6.0.0&new-version=7.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9c9ec6cf0..4e484e9a7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,7 +64,7 @@ jobs: run: | npm run prepare npm run build - - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: axe-core path: axe.js From f2b8935fecb44cd0821b067e49aff0cd8d9bf118 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:49:15 -0400 Subject: [PATCH 25/62] chore: bump the npm-low-risk group with 8 updates (#5053) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the npm-low-risk group with 8 updates: | Package | From | To | | --- | --- | --- | | [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) | `7.29.0` | `7.29.2` | | [@babel/runtime-corejs3](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime-corejs3) | `7.29.0` | `7.29.2` | | [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) | `3.48.0` | `3.49.0` | | [globals](https://github.com/sindresorhus/globals) | `17.3.0` | `17.4.0` | | [lint-staged](https://github.com/lint-staged/lint-staged) | `16.2.7` | `16.4.0` | | [serve-handler](https://github.com/vercel/serve-handler) | `6.1.6` | `6.1.7` | | [sinon](https://github.com/sinonjs/sinon) | `21.0.1` | `21.0.3` | | [start-server-and-test](https://github.com/bahmutov/start-server-and-test) | `2.1.3` | `2.1.5` | Updates `@babel/preset-env` from 7.29.0 to 7.29.2
    Release notes

    Sourced from @​babel/preset-env's releases.

    v7.29.2 (2026-03-16)

    :eyeglasses: Spec Compliance

    • babel-parser

    :bug: Bug Fix

    • babel-helpers, babel-plugin-transform-async-generator-functions, babel-preset-env, babel-runtime-corejs3
    • babel-preset-env
      • #17789 [7.x backport] preset-env include/exclude should accept bugfix plugins (@​JLHwung)

    :house: Internal

    Committers: 2

    v7.29.1 (2026-02-04)

    :bug: Bug Fix

    Committers: 2

    Commits

    Updates `@babel/runtime-corejs3` from 7.29.0 to 7.29.2
    Release notes

    Sourced from @​babel/runtime-corejs3's releases.

    v7.29.2 (2026-03-16)

    :eyeglasses: Spec Compliance

    • babel-parser

    :bug: Bug Fix

    • babel-helpers, babel-plugin-transform-async-generator-functions, babel-preset-env, babel-runtime-corejs3
    • babel-preset-env
      • #17789 [7.x backport] preset-env include/exclude should accept bugfix plugins (@​JLHwung)

    :house: Internal

    Committers: 2

    v7.29.1 (2026-02-04)

    :bug: Bug Fix

    Committers: 2

    Commits

    Updates `core-js` from 3.48.0 to 3.49.0
    Changelog

    Sourced from core-js's changelog.

    3.49.0 - 2026.03.16

    • Changes v3.48.0...v3.49.0 (373 commits)
    • Iterator.range updated following the actual spec version
      • Throw a RangeError on NaN start / end / step
      • Allow null as optionOrStep
    • Improved accuracy of Math.{ asinh, atanh } polyfills with big and small values
    • Improved accuracy of Number.prototype.toExponential polyfills with big and small values
    • Improved performance of atob, btoa, Uint8Array.fromHex, Uint8Array.prototype.setFromHex, and Uint8Array.prototype.toHex, #1503, #1464, #1510, thanks @​johnzhou721
    • Minor performance optimization polyfills of methods from Map upsert proposal
    • Polyfills of methods from Map upsert proposal from the pure version made generic to make it work with polyfilled and native collections
    • Wrap Symbol.for in Symbol.prototype.description polyfill for correct handling of empty string descriptions
    • Fixed a modern Safari bug in Array.prototype.includes with sparse arrays and fromIndex
    • Fixed one more case (Iterator.prototype.take) of a V8 ~ Chromium < 126 bug
    • Forced replacement of Iterator.{ concat, zip, zipKeyed } in the pure version for ensuring proper wrapped Iterator instances as the result
    • Fixed proxying .return() on exhausted iterator from some methods of iterator helpers polyfill to the underlying iterator
    • Fixed double .return() calling in case of throwing error in this method in the internal iterate helper that affected some polyfills
    • Fixed closing iterator on IteratorValue errors in the internal iterate helper that affected some polyfills
    • Fixed iterator closing in Array.from polyfill on failure to create array property
    • Fixed order of arguments validation in Array.fromAsync polyfill
    • Fixed a lack of counter validation on MAX_SAFE_INTEGER in Array.fromAsync polyfill
    • Fixed order of arguments validation in Array.prototype.flat polyfill
    • Fixed handling strings as iterables in Iterator.{ zip, zipKeyed } polyfills
    • Fixed some cases of iterators closing in Iterator.{ zip, zipKeyed } polyfills
    • Fixed validation of iterators .next() results an objects in Iterator.{ zip, zipKeyed } polyfills
    • Fixed a lack of early error in Iterator.concat polyfill on primitive as an iterator
    • Fixed buffer mutation exposure in Iterator.prototype.windows polyfill
    • Fixed iterator closing in Set.prototype.{ isDisjointFrom, isSupersetOf } polyfill
    • Fixed (updated following the final spec) one more case Set.prototype.difference polyfill with updating this
    • Fixed DataView.prototype.setFloat16 polyfill in (0, 1) range
    • Fixed order of arguments validation in String.prototype.{ padStart, padEnd } polyfills
    • Fixed order of arguments validation in String.prototype.{ startsWith, endsWith } polyfills
    • Fixed some cases of Infinity handling in String.prototype.substr polyfill
    • Fixed String.prototype.repeat polyfill with a counter exceeding 2 ** 32
    • Fixed some cases of chars case in escape polyfill
    • Fixed named backreferences in RegExp NCG polyfill
    • Fixed some cases of RegExp NCG polyfill in combination with other types of groups
    • Fixed some cases of RegExp NCG polyfill in combination with dotAll
    • Fixed String.prototype.replace with sticky polyfill, #810, #1514
    • Fixed RegExp sticky polyfill with alternation
    • Fixed handling of some line terminators in case of multiline + sticky mode in RegExp polyfill
    • Fixed .input slicing on result object with RegExp sticky mode polyfill
    • Fixed handling of empty groups with global and unicode modes in polyfills
    • Fixed URLSearchParam.prototype.delete polyfill with duplicate key-value pairs
    • Fixed possible removal of unnecessary entries in URLSearchParam.prototype.delete polyfill with second argument
    • Fixed an error in some cases of non-special URLs without a path in the URL polyfill
    • Fixed some percent encode cases / character sets in the URL polyfill
    • Fixed parsing of non-IPv4 hosts ends in a number in the URL polyfill
    • Fixed some cases of '' and null host handling in the URL polyfill
    • Fixed host parsing with hostname = host:port in the URL polyfill
    • Fixed host inheritance in some cases of file scheme in the URL polyfill

    ... (truncated)

    Commits
    • 80adfc4 v3.49.0
    • 0ad3e00 fix a modern Safari bug in Array.prototype.includes with sparse arrays and ...
    • 853bfa4 update some links
    • b4d723f fix a lack of counter validation on MAX_SAFE_INTEGER in Array.fromAsync p...
    • e276676 fix parsing of non-IPv4 hosts ends in a number in the URL polyfill
    • dd1cfba fix order of arguments validation in String.prototype.{ padStart, padEnd } ...
    • b952c5f add an extra protection to configurator
    • e490caf Fix for #810 (#1514)
    • 10b4e86 drop an unneeded comment
    • 28cf2e9 feat: Improve performance of Uint8Array Hex functions (#1510)
    • Additional commits viewable in compare view

    Updates `globals` from 17.3.0 to 17.4.0
    Release notes

    Sourced from globals's releases.

    v17.4.0

    • Update globals (2026-03-01) (#338) d43a051

    https://github.com/sindresorhus/globals/compare/v17.3.0...v17.4.0

    Commits

    Updates `lint-staged` from 16.2.7 to 16.4.0
    Release notes

    Sourced from lint-staged's releases.

    v16.4.0

    Minor Changes

    v16.3.4

    Patch Changes

    v16.3.3

    Patch Changes

    • #1740 0109e8d Thanks @​iiroj! - Make sure Git's warning about CRLF line-endings doesn't interfere with creating initial backup stash.

    v16.3.2

    Patch Changes

    • #1735 2adaf6c Thanks @​iiroj! - Hide the extra cmd window on Windows by spawning tasks without the detached option.

    v16.3.1

    Patch Changes

    • #1729 cd5d762 Thanks @​iiroj! - Remove nano-spawn as a dependency from package.json as it was replaced with tinyexec and is no longer used.

    v16.3.0

    Minor Changes

    • #1698 feda37a Thanks @​iiroj! - Run external processes with tinyexec instead of nano-spawn. nano-spawn replaced execa in lint-staged version 16 to limit the amount of npm dependencies required, but caused some unknown issues related to spawning tasks. Let's hope tinyexec improves the situation.

    • #1699 1346d16 Thanks @​iiroj! - Remove pidtree as a dependency. When a task fails, its sub-processes are killed more efficiently via the process group on Unix systems, and the taskkill command on Windows.

    Patch Changes

    • #1726 87467aa Thanks @​iiroj! - Incorrect brace expansions like *.{js} (nothing to expand) are detected exhaustively, instead of just a single pass.
    Changelog

    Sourced from lint-staged's changelog.

    16.4.0

    Minor Changes

    16.3.4

    Patch Changes

    16.3.3

    Patch Changes

    • #1740 0109e8d Thanks @​iiroj! - Make sure Git's warning about CRLF line-endings doesn't interfere with creating initial backup stash.

    16.3.2

    Patch Changes

    • #1735 2adaf6c Thanks @​iiroj! - Hide the extra cmd window on Windows by spawning tasks without the detached option.

    16.3.1

    Patch Changes

    • #1729 cd5d762 Thanks @​iiroj! - Remove nano-spawn as a dependency from package.json as it was replaced with tinyexec and is no longer used.

    16.3.0

    Minor Changes

    • #1698 feda37a Thanks @​iiroj! - Run external processes with tinyexec instead of nano-spawn. nano-spawn replaced execa in lint-staged version 16 to limit the amount of npm dependencies required, but caused some unknown issues related to spawning tasks. Let's hope tinyexec improves the situation.

    • #1699 1346d16 Thanks @​iiroj! - Remove pidtree as a dependency. When a task fails, its sub-processes are killed more efficiently via the process group on Unix systems, and the taskkill command on Windows.

    Patch Changes

    • #1726 87467aa Thanks @​iiroj! - Incorrect brace expansions like *.{js} (nothing to expand) are detected exhaustively, instead of just a single pass.
    Commits
    • 445f9dd chore(changeset): release
    • d91be60 docs: update readme to use picomatch
    • b392a9f refactor: extract matchFiles and add unit tests
    • 687fc90 refactor: replace micromatch with picomatch
    • 26dadf9 chore(changeset): release
    • 9d6e827 build(deps): update dependencies
    • 8aea986 chore(changeset): release
    • 0109e8d fix: strip Git CRLF warning from output
    • dfd6a7a chore(changeset): release
    • 2adaf6c fix(Windows): do not spawn tasks as detached since it opens a cmd window on ...
    • Additional commits viewable in compare view

    Updates `serve-handler` from 6.1.6 to 6.1.7
    Release notes

    Sourced from serve-handler's releases.

    6.1.7

    Patches

    • Fix: update minimatch to 3.1.5 to resolve security vulnerabilities: #228

    Credits

    Huge thanks to @​ParakhJaggi for helping!

    Commits

    Updates `sinon` from 21.0.1 to 21.0.3
    Changelog

    Sourced from sinon's changelog.

    21.0.3

    • 0494251b fix(#2678): upgrade samsam to fix buffer comparisons (Carl-Erik Kopseng)

    Released by Carl-Erik Kopseng on 2026-03-16.

    21.0.2

    • 024321c4 fix: skip Node specific tests in browser env (Carl-Erik Kopseng)
    • b836fccb fix: js-yaml breaking change from v3 to v4 (Carl-Erik Kopseng)
    • ebf0c431 docs: add how-to article for stubbing ES module imports (#1832) (#2676) (Eduard Barrera)
      • docs: add how-to article for stubbing ES module imports with esm package

      Adds a comprehensive How-To guide that addresses issue #1832, documenting how to configure Node.js to allow Sinon stubs to work with ES modules.

      • Explains why ES module namespace bindings are immutable by spec
      • Shows how to use the 'esm' npm package with mutableNamespace: true
      • Provides a complete working example with project layout, package.json, loader file, source modules, and a full test suite
      • Documents limitations (destructured imports, non-standard behavior)
      • Replaces the TODO comment in link-seams-commonjs.md with a cross-reference

      Closes #1832 Co-authored-by: Eduard Barrera eduardbar@users.noreply.github.com Co-authored-by: Carl-Erik Kopseng carlerik@gmail.com

    • ebcd506c Fix spies not being reset properly (#2673) (simon-id)
    • 3beab2ba Make doc tests pass with new jQuery (Carl-Erik Kopseng)
    • 766715c6 build: reduce transitive audit findings (Carl-Erik Kopseng)
    • 92aaf5c9 build: upgrade eslint config and replace dependency-check (Carl-Erik Kopseng)
    • c6aaa871 chore: ignore project worktrees (Carl-Erik Kopseng)
    • ef387e8e Upgrade most deps (Carl-Erik Kopseng)
    • 3cf4e77d docs: improve writing of documentation (#2675) (Eduardo de la Cruz Palacios)
    • 6349032f Check login status before publishing steps are performed (Carl-Erik Kopseng)

    Released by Carl-Erik Kopseng on 2026-03-04.

    Commits

    Updates `start-server-and-test` from 2.1.3 to 2.1.5
    Release notes

    Sourced from start-server-and-test's releases.

    v2.1.5

    2.1.5 (2026-02-24)

    Bug Fixes

    • formatting the message in the constructor of Error object (#395) (9d135de)

    v2.1.4

    2.1.4 (2026-02-24)

    Bug Fixes

    Commits
    Maintainer changes

    This version was pushed to npm by [GitHub Actions](https://www.npmjs.com/~GitHub Actions), a new releaser for start-server-and-test since your current version.


    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 421 ++++++++++++++++++++++++---------------------- 1 file changed, 219 insertions(+), 202 deletions(-) diff --git a/package-lock.json b/package-lock.json index fcdf925de..2d03b3175 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1543,9 +1543,9 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", - "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", + "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", "dev": true, "dependencies": { "@babel/compat-data": "^7.29.0", @@ -1641,9 +1641,9 @@ } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.0.tgz", - "integrity": "sha512-TgUkdp71C9pIbBcHudc+gXZnihEDOjUAmXO1VO4HHGES7QLZcShR0stfKIxLSNIYx2fqhmJChOjm/wkF8wv4gA==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.2.tgz", + "integrity": "sha512-Lc94FOD5+0aXhdb0Tdg3RUtqT6yWbI/BbFWvlaSJ3gAb9Ks+99nHRDKADVqC37er4eCB0fHyWT+y+K3QOvJKbw==", "dev": true, "dependencies": { "core-js-pure": "^3.48.0" @@ -2046,9 +2046,9 @@ "dev": true }, "node_modules/@hapi/tlds": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.4.tgz", - "integrity": "sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.6.tgz", + "integrity": "sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw==", "dev": true, "engines": { "node": ">=14.0.0" @@ -2426,18 +2426,18 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.0.tgz", - "integrity": "sha512-cqfapCxwTGsrR80FEgOoPsTonoefMBY7dnUEbQ+GRcved0jvkJLzvX6F4WtN+HBqbPX/SiFsIRUp+IrCW/2I2w==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.2.1.tgz", + "integrity": "sha512-QdfpQFIwYrTK8lFsII4bJ1AO1ZLbw7B+oxfP+/qSsiTrVerFp7aY2O+d2GNGrTxP58ezEbjbf7mTTLMsd7M7XQ==", "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.1" } }, "node_modules/@sinonjs/samsam": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", - "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-9.0.3.tgz", + "integrity": "sha512-ZgYY7Dc2RW+OUdnZ1DEHg00lhRt+9BjymPKHog4PRFzr1U3MbK57+djmscWyKxzO1qfunHqs4N45WWyKIFKpiQ==", "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.1", @@ -2460,9 +2460,9 @@ "dev": true }, "node_modules/@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true }, "node_modules/@testim/chrome-version": { @@ -2794,14 +2794,23 @@ } }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", + "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", "dev": true, "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios/node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "dev": true, + "engines": { + "node": ">=10" } }, "node_modules/babel-plugin-polyfill-corejs2": { @@ -3688,9 +3697,9 @@ } }, "node_modules/commander": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", - "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, "engines": { "node": ">=20" @@ -4247,9 +4256,9 @@ } }, "node_modules/core-js": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", - "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", "dev": true, "hasInstallScript": true, "funding": { @@ -5852,9 +5861,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, "funding": [ { @@ -5921,9 +5930,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "dependencies": { "asynckit": "^0.4.0", @@ -6389,9 +6398,9 @@ } }, "node_modules/globals": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", - "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", + "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", "dev": true, "engines": { "node": ">=18" @@ -7903,9 +7912,9 @@ } }, "node_modules/joi": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-18.0.2.tgz", - "integrity": "sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==", + "version": "18.1.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-18.1.2.tgz", + "integrity": "sha512-rF5MAmps5esSlhCA+N1b6IYHDw9j/btzGaqfgie522jS02Ju/HXBxamlXVlKEHAxoMKQL77HWI8jlqWsFuekZA==", "dev": true, "dependencies": { "@hapi/address": "^5.1.1", @@ -7914,7 +7923,7 @@ "@hapi/pinpoint": "^2.0.1", "@hapi/tlds": "^1.1.1", "@hapi/topo": "^6.0.2", - "@standard-schema/spec": "^1.0.0" + "@standard-schema/spec": "^1.1.0" }, "engines": { "node": ">= 20" @@ -8520,18 +8529,17 @@ } }, "node_modules/lint-staged": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", - "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", + "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==", "dev": true, "dependencies": { - "commander": "^14.0.2", + "commander": "^14.0.3", "listr2": "^9.0.5", - "micromatch": "^4.0.8", - "nano-spawn": "^2.0.0", - "pidtree": "^0.6.0", + "picomatch": "^4.0.3", "string-argv": "^0.3.2", - "yaml": "^2.8.1" + "tinyexec": "^1.0.4", + "yaml": "^2.8.2" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -8543,6 +8551,18 @@ "url": "https://opencollective.com/lint-staged" } }, + "node_modules/lint-staged/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/listr2": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", @@ -8685,9 +8705,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true }, "node_modules/lodash.debounce": { @@ -9230,9 +9250,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -9495,18 +9515,6 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, - "node_modules/nano-spawn": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", - "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", - "dev": true, - "engines": { - "node": ">=20.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -10510,18 +10518,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true, - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -11387,15 +11383,15 @@ } }, "node_modules/serve-handler": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", - "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz", + "integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==", "dev": true, "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", "mime-types": "2.1.18", - "minimatch": "3.1.2", + "minimatch": "3.1.5", "path-is-inside": "1.0.2", "path-to-regexp": "3.3.0", "range-parser": "1.2.0" @@ -11551,15 +11547,15 @@ "dev": true }, "node_modules/sinon": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.1.tgz", - "integrity": "sha512-Z0NVCW45W8Mg5oC/27/+fCqIHFnW8kpkFOq0j9XJIev4Ld0mKmERaZv5DMLAb9fGCevjKwaEeIQz5+MBXfZcDw==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.3.tgz", + "integrity": "sha512-0x8TQFr8EjADhSME01u1ZK31yv2+bd6Z5NrBCHVM+n4qL1wFqbxftmeyi3bwlr49FbbzRfrqSFOpyHCOh/YmYA==", "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^15.1.0", - "@sinonjs/samsam": "^8.0.3", - "diff": "^8.0.2", + "@sinonjs/fake-timers": "^15.1.1", + "@sinonjs/samsam": "^9.0.3", + "diff": "^8.0.3", "supports-color": "^7.2.0" }, "funding": { @@ -11568,9 +11564,9 @@ } }, "node_modules/sinon/node_modules/diff": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", - "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", "dev": true, "engines": { "node": ">=0.3.1" @@ -11947,9 +11943,9 @@ } }, "node_modules/start-server-and-test": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-2.1.3.tgz", - "integrity": "sha512-k4EcbNjeg0odaDkAMlIeDVDByqX9PIgL4tivgP2tES6Zd8o+4pTq/HgbWCyA3VHIoZopB+wGnNPKYGGSByNriQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-2.1.5.tgz", + "integrity": "sha512-A/SbXpgXE25ScSkpLLqvGvVZT0ykN6+AzS8tVqMBCTxbJy2Nwuen59opT+afalK5aS+AuQmZs0EsLwjnuDN+/g==", "dev": true, "dependencies": { "arg": "^5.0.2", @@ -11959,7 +11955,7 @@ "execa": "5.1.1", "lazy-ass": "1.6.0", "ps-tree": "1.2.0", - "wait-on": "9.0.3" + "wait-on": "9.0.4" }, "bin": { "server-test": "src/bin/start.js", @@ -12403,6 +12399,15 @@ "ms": "^2.1.1" } }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/tldts": { "version": "7.0.16", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.16.tgz", @@ -12867,14 +12872,14 @@ } }, "node_modules/wait-on": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.3.tgz", - "integrity": "sha512-13zBnyYvFDW1rBvWiJ6Av3ymAaq8EDQuvxZnPIw3g04UqGi4TyoIJABmfJ6zrvKo9yeFQExNkOk7idQbDJcuKA==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.4.tgz", + "integrity": "sha512-k8qrgfwrPVJXTeFY8tl6BxVHiclK11u72DVKhpybHfUL/K6KM4bdyK9EhIVYGytB5MJe/3lq4Tf0hrjM+pvJZQ==", "dev": true, "dependencies": { - "axios": "^1.13.2", - "joi": "^18.0.1", - "lodash": "^4.17.21", + "axios": "^1.13.5", + "joi": "^18.0.2", + "lodash": "^4.17.23", "minimist": "^1.2.8", "rxjs": "^7.8.2" }, @@ -13197,15 +13202,18 @@ "dev": true }, "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true, "bin": { "yaml": "bin.mjs" }, "engines": { "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yargs": { @@ -14291,9 +14299,9 @@ } }, "@babel/preset-env": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", - "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", + "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", "dev": true, "requires": { "@babel/compat-data": "^7.29.0", @@ -14380,9 +14388,9 @@ } }, "@babel/runtime-corejs3": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.0.tgz", - "integrity": "sha512-TgUkdp71C9pIbBcHudc+gXZnihEDOjUAmXO1VO4HHGES7QLZcShR0stfKIxLSNIYx2fqhmJChOjm/wkF8wv4gA==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.2.tgz", + "integrity": "sha512-Lc94FOD5+0aXhdb0Tdg3RUtqT6yWbI/BbFWvlaSJ3gAb9Ks+99nHRDKADVqC37er4eCB0fHyWT+y+K3QOvJKbw==", "dev": true, "requires": { "core-js-pure": "^3.48.0" @@ -14619,9 +14627,9 @@ "dev": true }, "@hapi/tlds": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.4.tgz", - "integrity": "sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.6.tgz", + "integrity": "sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw==", "dev": true }, "@hapi/topo": { @@ -14925,18 +14933,18 @@ } }, "@sinonjs/fake-timers": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.0.tgz", - "integrity": "sha512-cqfapCxwTGsrR80FEgOoPsTonoefMBY7dnUEbQ+GRcved0jvkJLzvX6F4WtN+HBqbPX/SiFsIRUp+IrCW/2I2w==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.2.1.tgz", + "integrity": "sha512-QdfpQFIwYrTK8lFsII4bJ1AO1ZLbw7B+oxfP+/qSsiTrVerFp7aY2O+d2GNGrTxP58ezEbjbf7mTTLMsd7M7XQ==", "dev": true, "requires": { "@sinonjs/commons": "^3.0.1" } }, "@sinonjs/samsam": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", - "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-9.0.3.tgz", + "integrity": "sha512-ZgYY7Dc2RW+OUdnZ1DEHg00lhRt+9BjymPKHog4PRFzr1U3MbK57+djmscWyKxzO1qfunHqs4N45WWyKIFKpiQ==", "dev": true, "requires": { "@sinonjs/commons": "^3.0.1", @@ -14958,9 +14966,9 @@ "dev": true }, "@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true }, "@testim/chrome-version": { @@ -15232,14 +15240,22 @@ "dev": true }, "axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", + "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", "dev": true, "requires": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + }, + "dependencies": { + "proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "dev": true + } } }, "babel-plugin-polyfill-corejs2": { @@ -15892,9 +15908,9 @@ } }, "commander": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", - "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true }, "compare-func": { @@ -16332,9 +16348,9 @@ "dev": true }, "core-js": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", - "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", "dev": true }, "core-js-compat": { @@ -17545,9 +17561,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true }, "for-in": { @@ -17584,9 +17600,9 @@ } }, "form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "requires": { "asynckit": "^0.4.0", @@ -17933,9 +17949,9 @@ } }, "globals": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", - "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", + "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", "dev": true }, "globule": { @@ -19012,9 +19028,9 @@ } }, "joi": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-18.0.2.tgz", - "integrity": "sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==", + "version": "18.1.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-18.1.2.tgz", + "integrity": "sha512-rF5MAmps5esSlhCA+N1b6IYHDw9j/btzGaqfgie522jS02Ju/HXBxamlXVlKEHAxoMKQL77HWI8jlqWsFuekZA==", "dev": true, "requires": { "@hapi/address": "^5.1.1", @@ -19023,7 +19039,7 @@ "@hapi/pinpoint": "^2.0.1", "@hapi/tlds": "^1.1.1", "@hapi/topo": "^6.0.2", - "@standard-schema/spec": "^1.0.0" + "@standard-schema/spec": "^1.1.0" } }, "jquery": { @@ -19516,18 +19532,25 @@ } }, "lint-staged": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", - "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", + "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==", "dev": true, "requires": { - "commander": "^14.0.2", + "commander": "^14.0.3", "listr2": "^9.0.5", - "micromatch": "^4.0.8", - "nano-spawn": "^2.0.0", - "pidtree": "^0.6.0", + "picomatch": "^4.0.3", "string-argv": "^0.3.2", - "yaml": "^2.8.1" + "tinyexec": "^1.0.4", + "yaml": "^2.8.2" + }, + "dependencies": { + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true + } } }, "listr2": { @@ -19631,9 +19654,9 @@ } }, "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true }, "lodash.debounce": { @@ -20017,9 +20040,9 @@ "dev": true }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -20206,12 +20229,6 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, - "nano-spawn": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", - "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", - "dev": true - }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -20981,12 +20998,6 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, - "pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true - }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -21648,15 +21659,15 @@ } }, "serve-handler": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", - "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz", + "integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==", "dev": true, "requires": { "bytes": "3.0.0", "content-disposition": "0.5.2", "mime-types": "2.1.18", - "minimatch": "3.1.2", + "minimatch": "3.1.5", "path-is-inside": "1.0.2", "path-to-regexp": "3.3.0", "range-parser": "1.2.0" @@ -21779,22 +21790,22 @@ "dev": true }, "sinon": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.1.tgz", - "integrity": "sha512-Z0NVCW45W8Mg5oC/27/+fCqIHFnW8kpkFOq0j9XJIev4Ld0mKmERaZv5DMLAb9fGCevjKwaEeIQz5+MBXfZcDw==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.3.tgz", + "integrity": "sha512-0x8TQFr8EjADhSME01u1ZK31yv2+bd6Z5NrBCHVM+n4qL1wFqbxftmeyi3bwlr49FbbzRfrqSFOpyHCOh/YmYA==", "dev": true, "requires": { "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^15.1.0", - "@sinonjs/samsam": "^8.0.3", - "diff": "^8.0.2", + "@sinonjs/fake-timers": "^15.1.1", + "@sinonjs/samsam": "^9.0.3", + "diff": "^8.0.3", "supports-color": "^7.2.0" }, "dependencies": { "diff": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", - "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", "dev": true } } @@ -22089,9 +22100,9 @@ } }, "start-server-and-test": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-2.1.3.tgz", - "integrity": "sha512-k4EcbNjeg0odaDkAMlIeDVDByqX9PIgL4tivgP2tES6Zd8o+4pTq/HgbWCyA3VHIoZopB+wGnNPKYGGSByNriQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-2.1.5.tgz", + "integrity": "sha512-A/SbXpgXE25ScSkpLLqvGvVZT0ykN6+AzS8tVqMBCTxbJy2Nwuen59opT+afalK5aS+AuQmZs0EsLwjnuDN+/g==", "dev": true, "requires": { "arg": "^5.0.2", @@ -22101,7 +22112,7 @@ "execa": "5.1.1", "lazy-ass": "1.6.0", "ps-tree": "1.2.0", - "wait-on": "9.0.3" + "wait-on": "9.0.4" }, "dependencies": { "debug": { @@ -22442,6 +22453,12 @@ } } }, + "tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "dev": true + }, "tldts": { "version": "7.0.16", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.16.tgz", @@ -22766,14 +22783,14 @@ } }, "wait-on": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.3.tgz", - "integrity": "sha512-13zBnyYvFDW1rBvWiJ6Av3ymAaq8EDQuvxZnPIw3g04UqGi4TyoIJABmfJ6zrvKo9yeFQExNkOk7idQbDJcuKA==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.4.tgz", + "integrity": "sha512-k8qrgfwrPVJXTeFY8tl6BxVHiclK11u72DVKhpybHfUL/K6KM4bdyK9EhIVYGytB5MJe/3lq4Tf0hrjM+pvJZQ==", "dev": true, "requires": { - "axios": "^1.13.2", - "joi": "^18.0.1", - "lodash": "^4.17.21", + "axios": "^1.13.5", + "joi": "^18.0.2", + "lodash": "^4.17.23", "minimist": "^1.2.8", "rxjs": "^7.8.2" } @@ -23015,9 +23032,9 @@ "dev": true }, "yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true }, "yargs": { From c6245e7aee824434fcdae3c77c24365493dbe4be Mon Sep 17 00:00:00 2001 From: yugo innami <58389827+nami8824@users.noreply.github.com> Date: Tue, 7 Apr 2026 23:29:50 +0900 Subject: [PATCH 26/62] fix(aria-allowed-attr): restrict br and wbr elements to aria-hidden only (#4974) Closes: https://github.com/dequelabs/axe-core/issues/3177 --- doc/standards-object.md | 3 +- .../aria/aria-allowed-attr-elm-evaluate.js | 45 +++++++++ lib/checks/aria/aria-allowed-attr-elm.json | 13 +++ lib/rules/aria-allowed-attr.json | 2 +- lib/standards/html-elms.js | 6 +- locales/_template.json | 7 ++ test/checks/aria/aria-allowed-attr-elm.js | 92 +++++++++++++++++++ .../rules/aria-allowed-attr/failures.html | 3 + .../rules/aria-allowed-attr/failures.json | 4 +- .../rules/aria-allowed-attr/passes.html | 4 + .../rules/aria-allowed-attr/passes.json | 4 +- 11 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 lib/checks/aria/aria-allowed-attr-elm-evaluate.js create mode 100644 lib/checks/aria/aria-allowed-attr-elm.json create mode 100644 test/checks/aria/aria-allowed-attr-elm.js diff --git a/doc/standards-object.md b/doc/standards-object.md index 42766eca3..eb460c179 100644 --- a/doc/standards-object.md +++ b/doc/standards-object.md @@ -95,7 +95,7 @@ The [`htmlElms`](../lib/standards/html-elms.js) object defines valid HTML elemen ### Used by Rules -- `aria-allowed-attr` - Checks if the attribute can be used on the element from the `noAriaAttrs` property. +- `aria-allowed-attr` - Checks if the attribute can be used on the element from the `noAriaAttrs` and `allowedAriaAttrs` properties. - `aria-allowed-role` - Checks if the role can be used on the HTML element from the `allowedRoles` property. - `aria-required-attrs` - Checks if any required attrs are defined implicitly on the element from the `implicitAttrs` property. @@ -110,6 +110,7 @@ The [`htmlElms`](../lib/standards/html-elms.js) object defines valid HTML elemen - `interactive` - `allowedRoles` - boolean or array(required). If element is allowed to use ARIA roles, a value of `true` means any role while a list of roles means only those are allowed. A value of `false` means no roles are allowed. - `noAriaAttrs` - boolean(optional. Defaults `true`). If the element is allowed to use global ARIA attributes and any allowed for the elements role. +- `allowedAriaAttrs` - array(optional). If specified, restricts which ARIA attributes may be used with this element (when no explicit role is set). Used by the `aria-allowed-attr` rule. - `shadowRoot` - boolean(optional. Default `false`). If the element is allowed to have a shadow root. - `implicitAttrs` - object(optional. Default `{}`). Any implicit ARIA attributes for the element and their default value. - `namingMethods` - array(optional. Default `[]`). The [native text method](../lib/commons/text/native-text-methods.js) used to calculate the accessible name of the element. diff --git a/lib/checks/aria/aria-allowed-attr-elm-evaluate.js b/lib/checks/aria/aria-allowed-attr-elm-evaluate.js new file mode 100644 index 000000000..01af3b4f2 --- /dev/null +++ b/lib/checks/aria/aria-allowed-attr-elm-evaluate.js @@ -0,0 +1,45 @@ +import { getExplicitRole } from '../../commons/aria'; +import { getElementSpec, getGlobalAriaAttrs } from '../../commons/standards'; + +export default function ariaAllowedAttrElmEvaluate(node, options, virtualNode) { + const elmSpec = getElementSpec(virtualNode); + + // If no allowedAriaAttrs restriction, this check doesn't apply + if (!elmSpec.allowedAriaAttrs) { + return true; + } + + // If element has an explicit role, defer to the role-based check + const explicitRole = getExplicitRole(virtualNode); + if (explicitRole) { + return true; + } + + const { allowedAriaAttrs } = elmSpec; + const globalAriaAttrs = getGlobalAriaAttrs(); + const invalid = []; + + for (const attrName of virtualNode.attrNames) { + if ( + globalAriaAttrs.includes(attrName) && + !allowedAriaAttrs.includes(attrName) + ) { + invalid.push(attrName); + } + } + + if (!invalid.length) { + return true; + } + + const messageKey = invalid.length > 1 ? 'plural' : 'singular'; + this.data({ + messageKey, + nodeName: virtualNode.props.nodeName, + values: invalid + .map(attrName => attrName + '="' + virtualNode.attr(attrName) + '"') + .join(', ') + }); + + return false; +} diff --git a/lib/checks/aria/aria-allowed-attr-elm.json b/lib/checks/aria/aria-allowed-attr-elm.json new file mode 100644 index 000000000..ebbdf67e9 --- /dev/null +++ b/lib/checks/aria/aria-allowed-attr-elm.json @@ -0,0 +1,13 @@ +{ + "id": "aria-allowed-attr-elm", + "evaluate": "aria-allowed-attr-elm-evaluate", + "metadata": { + "messages": { + "pass": "ARIA attributes are allowed for this element", + "fail": { + "singular": "ARIA attribute is not allowed on ${data.nodeName} elements: ${data.values}", + "plural": "ARIA attributes are not allowed on ${data.nodeName} elements: ${data.values}" + } + } + } +} diff --git a/lib/rules/aria-allowed-attr.json b/lib/rules/aria-allowed-attr.json index d90759692..4ef67632c 100644 --- a/lib/rules/aria-allowed-attr.json +++ b/lib/rules/aria-allowed-attr.json @@ -16,7 +16,7 @@ "description": "Ensure an element's role supports its ARIA attributes", "help": "Elements must only use supported ARIA attributes" }, - "all": ["aria-allowed-attr"], + "all": ["aria-allowed-attr", "aria-allowed-attr-elm"], "any": [], "none": ["aria-unsupported-attr"] } diff --git a/lib/standards/html-elms.js b/lib/standards/html-elms.js index 041d2a943..76e296f21 100644 --- a/lib/standards/html-elms.js +++ b/lib/standards/html-elms.js @@ -133,7 +133,8 @@ const htmlElms = { br: { contentTypes: ['phrasing', 'flow'], allowedRoles: ['presentation', 'none'], - namingMethods: ['titleText', 'singleSpace'] + namingMethods: ['titleText', 'singleSpace'], + allowedAriaAttrs: ['aria-hidden'] }, button: { contentTypes: ['interactive', 'phrasing', 'flow'], @@ -969,7 +970,8 @@ const htmlElms = { }, wbr: { contentTypes: ['phrasing', 'flow'], - allowedRoles: ['presentation', 'none'] + allowedRoles: ['presentation', 'none'], + allowedAriaAttrs: ['aria-hidden'] } }; diff --git a/locales/_template.json b/locales/_template.json index 3a2144901..2e7d53c4a 100644 --- a/locales/_template.json +++ b/locales/_template.json @@ -430,6 +430,13 @@ "plural": "Abstract roles cannot be directly used: ${data.values}" } }, + "aria-allowed-attr-elm": { + "pass": "ARIA attributes are allowed for this element", + "fail": { + "singular": "ARIA attribute is not allowed on ${data.nodeName} elements: ${data.values}", + "plural": "ARIA attributes are not allowed on ${data.nodeName} elements: ${data.values}" + } + }, "aria-allowed-attr": { "pass": "ARIA attributes are used correctly for the defined role", "fail": { diff --git a/test/checks/aria/aria-allowed-attr-elm.js b/test/checks/aria/aria-allowed-attr-elm.js new file mode 100644 index 000000000..2d90568e2 --- /dev/null +++ b/test/checks/aria/aria-allowed-attr-elm.js @@ -0,0 +1,92 @@ +describe('aria-allowed-attr-elm', () => { + 'use strict'; + + const queryFixture = axe.testUtils.queryFixture; + const checkContext = axe.testUtils.MockCheckContext(); + + afterEach(() => { + checkContext.reset(); + }); + + it('should pass for br with aria-hidden', () => { + const vNode = queryFixture(''); + + assert.isTrue( + axe.testUtils + .getCheckEvaluate('aria-allowed-attr-elm') + .call(checkContext, null, null, vNode) + ); + }); + + it('should pass for wbr with aria-hidden', () => { + const vNode = queryFixture(''); + + assert.isTrue( + axe.testUtils + .getCheckEvaluate('aria-allowed-attr-elm') + .call(checkContext, null, null, vNode) + ); + }); + + it('should fail for br with disallowed aria attribute', () => { + const vNode = queryFixture('
    '); + + assert.isFalse( + axe.testUtils + .getCheckEvaluate('aria-allowed-attr-elm') + .call(checkContext, null, null, vNode) + ); + assert.deepEqual(checkContext._data, { + messageKey: 'singular', + nodeName: 'br', + values: 'aria-busy="true"' + }); + }); + + it('should fail for wbr with disallowed aria attribute', () => { + const vNode = queryFixture(''); + + assert.isFalse( + axe.testUtils + .getCheckEvaluate('aria-allowed-attr-elm') + .call(checkContext, null, null, vNode) + ); + assert.deepEqual(checkContext._data, { + messageKey: 'singular', + nodeName: 'wbr', + values: 'aria-busy="true"' + }); + }); + + it('should pass for br with non-global aria attribute', () => { + const vNode = queryFixture('
    '); + + assert.isTrue( + axe.testUtils + .getCheckEvaluate('aria-allowed-attr-elm') + .call(checkContext, null, null, vNode) + ); + }); + + it('should pass for br with explicit role', () => { + const vNode = queryFixture( + '
    ' + ); + + assert.isTrue( + axe.testUtils + .getCheckEvaluate('aria-allowed-attr-elm') + .call(checkContext, null, null, vNode) + ); + }); + + it('should pass for element without allowedAriaAttrs restriction', () => { + const vNode = queryFixture('
    '); + + assert.isTrue( + axe.testUtils + .getCheckEvaluate('aria-allowed-attr-elm') + .call(checkContext, null, null, vNode) + ); + }); +}); diff --git a/test/integration/rules/aria-allowed-attr/failures.html b/test/integration/rules/aria-allowed-attr/failures.html index 21c84b0e5..4c582d7d4 100644 --- a/test/integration/rules/aria-allowed-attr/failures.html +++ b/test/integration/rules/aria-allowed-attr/failures.html @@ -13,3 +13,6 @@
    + +
    + diff --git a/test/integration/rules/aria-allowed-attr/failures.json b/test/integration/rules/aria-allowed-attr/failures.json index 02d9579b8..03a4569f5 100644 --- a/test/integration/rules/aria-allowed-attr/failures.json +++ b/test/integration/rules/aria-allowed-attr/failures.json @@ -9,6 +9,8 @@ ["#fail5"], ["#fail6"], ["#fail7"], - ["#fail8"] + ["#fail8"], + ["#fail9"], + ["#fail10"] ] } diff --git a/test/integration/rules/aria-allowed-attr/passes.html b/test/integration/rules/aria-allowed-attr/passes.html index a21c68c3b..3242c3170 100644 --- a/test/integration/rules/aria-allowed-attr/passes.html +++ b/test/integration/rules/aria-allowed-attr/passes.html @@ -2173,3 +2173,7 @@
    + + +
    + diff --git a/test/integration/rules/aria-allowed-attr/passes.json b/test/integration/rules/aria-allowed-attr/passes.json index 0974e0725..6c68e108a 100644 --- a/test/integration/rules/aria-allowed-attr/passes.json +++ b/test/integration/rules/aria-allowed-attr/passes.json @@ -104,6 +104,8 @@ ["#pass99"], ["#pass100"], ["#pass101"], - ["#pass102"] + ["#pass102"], + ["#pass103"], + ["#pass104"] ] } From be3801e286dd812d455b697671202970fc3eaf11 Mon Sep 17 00:00:00 2001 From: Rin Date: Thu, 9 Apr 2026 23:23:47 +0700 Subject: [PATCH 27/62] refactor(frame-messenger): Guard against inherited properties as topics and channelIds (#5062) Updated `frame-messenger` and `respondable` modules to use `Object.create(null)` for message and topic handler stores. ### Description The current implementation uses plain objects (`{}`) which are susceptible to prototype pollution if untrusted strings like `"__proto__"` are passed as `channelId` or `topic`. By using `Object.create(null)`, these stores become "pure" maps without a prototype, ensuring security and robustness in cross-frame communication. This change is backward compatible and adheres to standard security practices for high-performance JavaScript libraries. Closes: #5062 --- lib/core/utils/frame-messenger/channel-store.js | 6 ++++-- lib/core/utils/respondable.js | 13 +++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/core/utils/frame-messenger/channel-store.js b/lib/core/utils/frame-messenger/channel-store.js index 59ba07878..1cb12256c 100644 --- a/lib/core/utils/frame-messenger/channel-store.js +++ b/lib/core/utils/frame-messenger/channel-store.js @@ -8,14 +8,16 @@ export function storeReplyHandler( sendToParent = true ) { assert( - !channels[channelId], + !Object.prototype.hasOwnProperty.call(channels, channelId), `A replyHandler already exists for this message channel.` ); channels[channelId] = { replyHandler, sendToParent }; } export function getReplyHandler(channelId) { - return channels[channelId]; + return Object.prototype.hasOwnProperty.call(channels, channelId) + ? channels[channelId] + : undefined; } export function deleteReplyHandler(channelId) { diff --git a/lib/core/utils/respondable.js b/lib/core/utils/respondable.js index 7a1c5c4da..cbacaf932 100644 --- a/lib/core/utils/respondable.js +++ b/lib/core/utils/respondable.js @@ -39,7 +39,13 @@ export default function respondable( */ function messageListener(data, responder) { const { topic, message, keepalive } = data; - const topicHandler = topicHandlers[topic]; + const topicHandler = Object.prototype.hasOwnProperty.call( + topicHandlers, + topic + ) + ? topicHandlers[topic] + : undefined; + if (!topicHandler) { return; } @@ -92,7 +98,10 @@ respondable.subscribe = function subscribe(topic, topicHandler) { typeof topicHandler === 'function', 'Subscriber callback must be a function' ); - assert(!topicHandlers[topic], `Topic ${topic} is already registered to.`); + assert( + !Object.prototype.hasOwnProperty.call(topicHandlers, topic), + `Topic ${topic} is already registered to.` + ); topicHandlers[topic] = topicHandler; }; From 1229a6e7162768a283f5e2307024dee0d0566452 Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Fri, 10 Apr 2026 08:46:42 -0600 Subject: [PATCH 28/62] fix(target-size): ignore position: fixed elements that are offscreen when page is scrolled (#5066) closes #5065 --------- Co-authored-by: Wilco Fiers Co-authored-by: Wilco Fiers --- lib/commons/dom/find-nearby-elms.js | 16 +- lib/commons/dom/index.js | 1 + lib/commons/dom/is-fixed-position.js | 45 +++ lib/commons/dom/is-offscreen.js | 43 +-- test/commons/dom/is-fixed-position.js | 104 ++++++ test/commons/dom/is-offscreen.js | 310 +++++++++++------- test/commons/dom/is-visible.js | 26 ++ .../meta-refresh-pass.js | 1 - .../full/target-size/fixed-scroll.html | 40 +++ .../full/target-size/fixed-scroll.js | 45 +++ .../full/target-size/target-size.js | 1 - .../full/target-size/too-many-rects.js | 1 - .../integration/virtual-rules/summary-name.js | 1 - 13 files changed, 486 insertions(+), 148 deletions(-) create mode 100644 lib/commons/dom/is-fixed-position.js create mode 100644 test/commons/dom/is-fixed-position.js create mode 100644 test/integration/full/target-size/fixed-scroll.html create mode 100644 test/integration/full/target-size/fixed-scroll.js diff --git a/lib/commons/dom/find-nearby-elms.js b/lib/commons/dom/find-nearby-elms.js index a0e421137..b6e69e52b 100644 --- a/lib/commons/dom/find-nearby-elms.js +++ b/lib/commons/dom/find-nearby-elms.js @@ -1,5 +1,5 @@ import getNodeGrid from './get-node-grid'; -import { memoize } from '../../core/utils'; +import isFixedPosition from './is-fixed-position'; export default function findNearbyElms(vNode, margin = 0) { const grid = getNodeGrid(vNode); @@ -7,7 +7,7 @@ export default function findNearbyElms(vNode, margin = 0) { return []; // Elements not in the grid don't have ._grid } const rect = vNode.boundingClientRect; - const selfIsFixed = hasFixedPosition(vNode); + const selfIsFixed = isFixedPosition(vNode); const gridPosition = grid.getGridPositionOfRect(rect, margin); const neighbors = []; @@ -17,7 +17,7 @@ export default function findNearbyElms(vNode, margin = 0) { vNeighbor && vNeighbor !== vNode && !neighbors.includes(vNeighbor) && - selfIsFixed === hasFixedPosition(vNeighbor) + selfIsFixed === isFixedPosition(vNeighbor) ) { neighbors.push(vNeighbor); } @@ -26,13 +26,3 @@ export default function findNearbyElms(vNode, margin = 0) { return neighbors; } - -const hasFixedPosition = memoize(vNode => { - if (!vNode) { - return false; - } - if (vNode.getComputedStylePropertyValue('position') === 'fixed') { - return true; - } - return hasFixedPosition(vNode.parent); -}); diff --git a/lib/commons/dom/index.js b/lib/commons/dom/index.js index 3449238aa..93e3f353a 100644 --- a/lib/commons/dom/index.js +++ b/lib/commons/dom/index.js @@ -28,6 +28,7 @@ export { default as hasLangText } from './has-lang-text'; export { default as idrefs } from './idrefs'; export { default as insertedIntoFocusOrder } from './inserted-into-focus-order'; export { default as isCurrentPageLink } from './is-current-page-link'; +export { default as isFixedPosition } from './is-fixed-position'; export { default as isFocusable } from './is-focusable'; export { default as isHiddenWithCSS } from './is-hidden-with-css'; export { default as isHiddenForEveryone } from './is-hidden-for-everyone'; diff --git a/lib/commons/dom/is-fixed-position.js b/lib/commons/dom/is-fixed-position.js new file mode 100644 index 000000000..d065694cf --- /dev/null +++ b/lib/commons/dom/is-fixed-position.js @@ -0,0 +1,45 @@ +import memoize from '../../core/utils/memoize'; +import { nodeLookup } from '../../core/utils'; + +/** + * Determines if an element is inside a position:fixed subtree, even if the element itself is positioned differently. + * @param {VirtualNode|Element} node + * @param {Boolean} [options.skipAncestors] If the ancestor tree should not be used + * @return {Boolean} The element's position state + */ +export default function isFixedPosition(node, { skipAncestors } = {}) { + const { vNode } = nodeLookup(node); + + // detached element + if (!vNode) { + return false; + } + + if (skipAncestors) { + return isFixedSelf(vNode); + } + + return isFixedAncestors(vNode); +} + +/** + * Check the element for position:fixed + */ +const isFixedSelf = memoize(function isFixedSelfMemoized(vNode) { + return vNode.getComputedStylePropertyValue('position') === 'fixed'; +}); + +/** + * Check the element and ancestors for position:fixed + */ +const isFixedAncestors = memoize(function isFixedAncestorsMemoized(vNode) { + if (isFixedSelf(vNode)) { + return true; + } + + if (!vNode.parent) { + return false; + } + + return isFixedAncestors(vNode.parent); +}); diff --git a/lib/commons/dom/is-offscreen.js b/lib/commons/dom/is-offscreen.js index d0e3b75f7..4883be568 100644 --- a/lib/commons/dom/is-offscreen.js +++ b/lib/commons/dom/is-offscreen.js @@ -2,6 +2,7 @@ import getComposedParent from './get-composed-parent'; import getElementCoordinates from './get-element-coordinates'; import getViewportSize from './get-viewport-size'; import { nodeLookup } from '../../core/utils'; +import isFixedPosition from './is-fixed-position'; function noParentScrolled(element, offset) { element = getComposedParent(element); @@ -37,39 +38,43 @@ function isOffscreen(element, { isAncestor } = {}) { return undefined; } - let leftBoundary; const docElement = document.documentElement; const styl = window.getComputedStyle(domNode); const dir = window .getComputedStyle(document.body || docElement) .getPropertyValue('direction'); - const coords = getElementCoordinates(domNode); + const isFixed = isFixedPosition(domNode); + const coords = isFixed + ? domNode.getBoundingClientRect() + : getElementCoordinates(domNode); + + // Consider 0 height/ width elements at origin visible + if (coords.top === 0 && coords.bottom === 0) { + return false; + } + if (coords.left === 0 && coords.right === 0) { + return false; + } - // bottom edge beyond if ( - coords.bottom < 0 && + coords.bottom <= 0 && (noParentScrolled(domNode, coords.bottom) || styl.position === 'absolute') ) { return true; } - if (coords.left === 0 && coords.right === 0) { - //This is an edge case, an empty (zero-width) element that isn't positioned 'off screen'. - return false; + const viewportSize = getViewportSize(window); + if (isFixed && coords.top >= viewportSize.height) { + return true; // Positioned below the viewport } - if (dir === 'ltr') { - if (coords.right <= 0) { - return true; - } - } else { - leftBoundary = Math.max( - docElement.scrollWidth, - getViewportSize(window).width - ); - if (coords.left >= leftBoundary) { - return true; - } + const rightEdge = Math.max(docElement.scrollWidth, viewportSize.width); + if ((isFixed || dir === 'rtl') && coords.left >= rightEdge) { + return true; // Positioned right of the viewport, preventing right scrolling + } + + if ((isFixed || dir === 'ltr') && coords.right <= 0) { + return true; // Positioned left of the viewport, preventing left scrolling } return false; diff --git a/test/commons/dom/is-fixed-position.js b/test/commons/dom/is-fixed-position.js new file mode 100644 index 000000000..c6ff247e4 --- /dev/null +++ b/test/commons/dom/is-fixed-position.js @@ -0,0 +1,104 @@ +describe('dom.isFixedPosition', () => { + const isFixedPosition = axe.commons.dom.isFixedPosition; + const { queryFixture, queryShadowFixture } = axe.testUtils; + + it('returns true for element with "position: fixed"', () => { + const vNode = queryFixture( + '
    ' + ); + + assert.isTrue(isFixedPosition(vNode)); + }); + + it('returns false for element without position', () => { + const vNode = queryFixture('
    '); + + assert.isFalse(isFixedPosition(vNode)); + }); + + for (const position of ['relative', 'absolute', 'sticky']) { + it(`returns false for element with "position: ${position}"`, () => { + const vNode = queryFixture( + `
    ` + ); + + assert.isFalse(isFixedPosition(vNode)); + }); + } + + it('returns true for ancestor with position: fixed', () => { + const vNode = queryFixture( + '
    ' + ); + + assert.isTrue(isFixedPosition(vNode)); + }); + + it('returns true for ancestor with "position: fixed" even when the element is positioned differently', () => { + const vNode = queryFixture( + '
    ' + ); + + assert.isTrue(isFixedPosition(vNode)); + }); + + it('returns false on detached elements', function () { + var el = document.createElement('div'); + el.innerHTML = 'I am not visible because I am detached!'; + + assert.isFalse(axe.commons.dom.isFixedPosition(el)); + }); + + describe('options.skipAncestors', () => { + it('returns false for ancestor with "position: fixed"', () => { + const vNode = queryFixture( + '
    ' + ); + + assert.isFalse(isFixedPosition(vNode, { skipAncestors: true })); + }); + }); + + describe('Shadow DOM', () => { + it('returns false when no element in the composed tree has position: fixed', () => { + const vNode = queryShadowFixture( + '
    ', + '' + ); + assert.isFalse(isFixedPosition(vNode)); + }); + + it('returns true for element in shadow tree with position: fixed', () => { + const vNode = queryShadowFixture( + '
    ', + '
    ' + ); + + assert.isTrue(isFixedPosition(vNode)); + }); + + it('returns true when ancestor outside shadow tree has position: fixed', () => { + const vNode = queryShadowFixture( + '
    ', + '' + ); + assert.isTrue(isFixedPosition(vNode)); + }); + + it('returns true when ancestor outside shadow is fixed and target in shadow has a different position', () => { + const vNode = queryShadowFixture( + '
    ', + '' + ); + assert.isTrue(isFixedPosition(vNode)); + }); + + it('returns false with skipAncestors when only ancestor outside shadow tree is fixed', () => { + const vNode = queryShadowFixture( + '
    ', + '' + ); + assert.isFalse(isFixedPosition(vNode, { skipAncestors: true })); + }); + }); +}); diff --git a/test/commons/dom/is-offscreen.js b/test/commons/dom/is-offscreen.js index e755eda1a..4455c3a16 100644 --- a/test/commons/dom/is-offscreen.js +++ b/test/commons/dom/is-offscreen.js @@ -1,161 +1,247 @@ -describe('dom.isOffscreen', function () { - 'use strict'; - var fixture = document.getElementById('fixture'); - var shadowSupport = axe.testUtils.shadowSupport; +describe('dom.isOffscreen', () => { + const { isOffscreen } = axe.commons.dom; + const fixture = document.getElementById('fixture'); + const { queryFixture, fixtureSetup, flatTreeSetup, shadowSupport } = + axe.testUtils; - afterEach(function () { - fixture.innerHTML = ''; + afterEach(() => { document.body.style.direction = 'ltr'; }); - it('should detect elements positioned outside the left edge', function () { - fixture.innerHTML = - '
    Offscreen?
    '; - var el = document.getElementById('target'); + after(() => { + window.scrollTo(0, 0); + }); + + it('should be false for 0 height elements at the top of the viewport', () => { + assert.isFalse(isOffscreen(document.body)); + assert.isFalse(isOffscreen(document.documentElement)); + + const vNode = queryFixture('
    '); + assert.isFalse(isOffscreen(vNode)); + }); - assert.isTrue(axe.commons.dom.isOffscreen(el)); + it('should detect elements positioned outside the left edge', () => { + const vNode = queryFixture( + '
    Offscreen?
    ' + ); + assert.isTrue(isOffscreen(vNode)); }); - it('should detect elements positioned to but not beyond the left edge', function () { - fixture.innerHTML = - '
    Offscreen?
    '; - var el = document.getElementById('target'); + it('should detect elements positioned to but not beyond the left edge', () => { + const vNode = queryFixture( + '
    Offscreen?
    ' + ); - assert.isTrue(axe.commons.dom.isOffscreen(el)); + assert.isTrue(isOffscreen(vNode)); }); - it('should not detect elements at the left edge with a zero width', function () { - fixture.innerHTML = - '
    '; - var el = document.getElementById('target'); + it('should not detect elements at the left edge with a zero width', () => { + const vNode = queryFixture( + '
    ' + ); - assert.isFalse(axe.commons.dom.isOffscreen(el)); + assert.isFalse(isOffscreen(vNode)); }); - it('should detect elements positioned outside the top edge', function () { - fixture.innerHTML = - '
    Offscreen?
    '; - var el = document.getElementById('target'); - assert.isTrue(axe.commons.dom.isOffscreen(el)); + it('should detect elements positioned outside the top edge', () => { + const vNode = queryFixture( + '
    Offscreen?
    ' + ); + assert.isTrue(isOffscreen(vNode)); }); - it('should never detect elements positioned outside the bottom edge', function () { - fixture.innerHTML = - '
    Offscreen?
    '; - var el = document.getElementById('target'); + it('should never detect elements positioned outside the bottom edge', () => { + const vNode = queryFixture( + '
    Offscreen?
    ' + ); - assert.isFalse(axe.commons.dom.isOffscreen(el)); + assert.isFalse(isOffscreen(vNode)); }); - it('should detect elements positioned that bleed inside the left edge', function () { - fixture.innerHTML = - '
    Offscreen?
    '; - var el = document.getElementById('target'); + it('should detect elements positioned that bleed inside the left edge', () => { + const vNode = queryFixture( + '
    Offscreen?
    ' + ); - assert.isFalse(axe.commons.dom.isOffscreen(el)); + assert.isFalse(isOffscreen(vNode)); }); - it('should detect elements positioned outside the right edge', function () { - fixture.innerHTML = - '
    Offscreen?
    '; - var el = document.getElementById('target'); + it('should detect elements positioned outside the right edge', () => { + const vNode = queryFixture( + '
    Offscreen?
    ' + ); - assert.isFalse(axe.commons.dom.isOffscreen(el)); + assert.isFalse(isOffscreen(vNode)); }); - it('should detect elements positioned outside the top edge', function () { - fixture.innerHTML = - '
    Offscreen?
    '; - var el = document.getElementById('target'); + it('should detect elements positioned outside the top edge', () => { + const vNode = queryFixture( + '
    Offscreen?
    ' + ); - assert.isFalse(axe.commons.dom.isOffscreen(el)); + assert.isFalse(isOffscreen(vNode)); }); - it('should detect elements positioned outside the bottom edge', function () { - fixture.innerHTML = - '
    Offscreen?
    '; - var el = document.getElementById('target'); + it('should detect elements positioned outside the bottom edge', () => { + const vNode = queryFixture( + '
    Offscreen?
    ' + ); - assert.isFalse(axe.commons.dom.isOffscreen(el)); + assert.isFalse(isOffscreen(vNode)); }); - it('should detect elements that are made off-screen by a parent', function () { - fixture.innerHTML = - '
    ' + - '
    Offscreen?
    ' + - '
    '; - - var el = document.getElementById('target'); + it('should detect elements that are made off-screen by a parent', () => { + const vNode = queryFixture(` +
    +
    Offscreen?
    +
    + `); - assert.isTrue(axe.commons.dom.isOffscreen(el)); + assert.isTrue(isOffscreen(vNode)); }); - it('should NOT detect elements positioned outside the right edge on LTR documents', function () { - fixture.innerHTML = - '
    Offscreen?
    '; - var el = document.getElementById('target'); + it('should NOT detect elements positioned outside the right edge on LTR documents', () => { + const vNode = queryFixture( + '
    Offscreen?
    ' + ); - assert.isFalse(axe.commons.dom.isOffscreen(el)); + assert.isFalse(isOffscreen(vNode)); }); - it('should detect elements positioned outside the right edge on RTL documents', function () { + it('should detect elements positioned outside the right edge on RTL documents', () => { document.body.style.direction = 'rtl'; - fixture.innerHTML = - '
    Offscreen?
    '; - var el = document.getElementById('target'); + const vNode = queryFixture( + '
    Offscreen?
    ' + ); + assert.isTrue(isOffscreen(vNode)); + }); - assert.isTrue(axe.commons.dom.isOffscreen(el)); + it('should NOT detect elements positioned outside the left edge on RTL documents', () => { + document.body.style.direction = 'rtl'; + const vNode = queryFixture( + '
    Offscreen?
    ' + ); + + assert.isFalse(isOffscreen(vNode)); }); - it('should NOT detect elements positioned outside the left edge on RTL documents', function () { + it('should detect elements positioned outside the right edge on RTL documents', () => { document.body.style.direction = 'rtl'; - fixture.innerHTML = - '
    Offscreen?
    '; - var el = document.getElementById('target'); - - assert.isFalse(axe.commons.dom.isOffscreen(el)); - }); - - it('should not detect elements positioned because of a scroll', function () { - fixture.innerHTML = - '
    ' + - '
    goobye
    ' + - '
    high
    ' + - '
    hello
    ' + - '
    '; - var viz = document.getElementById('visible'); - assert.isFalse(axe.commons.dom.isOffscreen(viz)); - var scrollme = document.getElementById('scrollme'); + const vNode = queryFixture( + '
    Offscreen?
    ' + ); + + assert.isTrue(isOffscreen(vNode)); + }); + + it('should not detect elements positioned because of a scroll', () => { + fixtureSetup(` +
    +
    goodbye
    +
    high
    +
    hello
    +
    + `); + const viz = document.getElementById('visible'); + assert.isFalse(isOffscreen(viz)); + const scrollme = document.getElementById('scrollme'); scrollme.scrollIntoView(); - assert.isFalse(axe.commons.dom.isOffscreen(viz)); + assert.isFalse(isOffscreen(viz)); }); - it('should return undefined if actual ndoe is undefined', function () { - assert.isUndefined(axe.commons.dom.isOffscreen()); + it('should return undefined if actual node is undefined', () => { + assert.isUndefined(isOffscreen()); }); - (shadowSupport.v1 ? it : xit)( - 'should detect on screen shadow nodes', - function () { - fixture.innerHTML = '
    '; - var shadow = fixture.querySelector('div').attachShadow({ mode: 'open' }); - shadow.innerHTML = '
    Offscreen?
    '; - - var el = shadow.querySelector('#target'); - assert.isFalse(axe.commons.dom.isOffscreen(el)); - } - ); + (shadowSupport.v1 ? it : xit)('should detect on screen shadow nodes', () => { + fixture.innerHTML = '
    '; + const shadow = fixture.querySelector('div').attachShadow({ mode: 'open' }); + shadow.innerHTML = '
    Offscreen?
    '; + flatTreeSetup(fixture); - (shadowSupport.v1 ? it : xit)( - 'should detect off screen shadow nodes', - function () { - fixture.innerHTML = '
    '; - var shadow = fixture.querySelector('div').attachShadow({ mode: 'open' }); - shadow.innerHTML = - '
    Offscreen?
    '; + const el = shadow.querySelector('#target'); + assert.isFalse(isOffscreen(el)); + }); - var el = shadow.querySelector('#target'); - assert.isTrue(axe.commons.dom.isOffscreen(el)); - } - ); + (shadowSupport.v1 ? it : xit)('should detect off screen shadow nodes', () => { + fixture.innerHTML = '
    '; + const shadow = fixture.querySelector('div').attachShadow({ mode: 'open' }); + shadow.innerHTML = + '
    Offscreen?
    '; + flatTreeSetup(fixture); + + const el = shadow.querySelector('#target'); + assert.isTrue(isOffscreen(el)); + }); + + describe('positioned: fixed', () => { + it('should detect elements positioned outside the top edge', () => { + const vNode = queryFixture( + '
    Offscreen?
    ' + ); + + assert.isTrue(isOffscreen(vNode)); + }); + + it('should detect elements positioned outside the top edge when scrolled', () => { + const vNode = queryFixture(` +
    +
    Offscreen?
    +
    + `); + + assert.isTrue(isOffscreen(vNode)); + window.scrollTo(0, document.body.scrollHeight); + assert.isTrue(isOffscreen(vNode)); + }); + + it('should detect elements positioned outside the bottom edge', () => { + const vNode = queryFixture( + `
    Offscreen?
    ` + ); + assert.isTrue(isOffscreen(vNode)); + }); + + it('should consider elements in the viewport, but beyond the window size as on screen', () => { + const vNode = queryFixture(` +
    +

    Hello World

    +
    + Offscreen? +
    +
    + `); + assert.isFalse(isOffscreen(vNode)); + }); + + it('should detect elements positioned outside the right edge (LTR)', () => { + const vNode = queryFixture( + `
    Offscreen?
    ` + ); + assert.isTrue(isOffscreen(vNode)); + }); + + it('should detect elements positioned outside the right edge (RTL)', () => { + document.body.style.direction = 'rtl'; + const vNode = queryFixture( + `
    Offscreen?
    ` + ); + assert.isTrue(isOffscreen(vNode)); + }); + + it('should detect elements positioned outside the left edge (LTR)', () => { + const vNode = queryFixture( + `
    Offscreen?
    ` + ); + assert.isTrue(isOffscreen(vNode)); + }); + + it('should detect elements positioned outside the left edge on RTL documents', () => { + document.body.style.direction = 'rtl'; + const vNode = queryFixture( + `
    Offscreen?
    ` + ); + assert.isTrue(isOffscreen(vNode)); + }); + }); }); diff --git a/test/commons/dom/is-visible.js b/test/commons/dom/is-visible.js index db85fafc3..7ea710a2a 100644 --- a/test/commons/dom/is-visible.js +++ b/test/commons/dom/is-visible.js @@ -5,6 +5,7 @@ describe('dom.isVisible', function () { var queryFixture = axe.testUtils.queryFixture; var shadowSupported = axe.testUtils.shadowSupport.v1; var computedStyleStub; + var flatTreeSetup = axe.testUtils.flatTreeSetup; afterEach(function () { document.getElementById('fixture').innerHTML = ''; @@ -21,6 +22,7 @@ describe('dom.isVisible', function () { it('should return false if computedStyle return null for whatever reason', function () { computedStyleStub = sinon.stub(window, 'getComputedStyle').returns(null); var el = document.createElement('div'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); @@ -28,6 +30,7 @@ describe('dom.isVisible', function () { fixture.innerHTML = '
    Hello!
    '; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); @@ -36,6 +39,7 @@ describe('dom.isVisible', function () { '
    hi
    '; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); @@ -44,6 +48,7 @@ describe('dom.isVisible', function () { '
    StickySticky
    '; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); @@ -53,6 +58,7 @@ describe('dom.isVisible', function () { '
    Hi
    ' + '
'; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); @@ -60,6 +66,7 @@ describe('dom.isVisible', function () { fixture.innerHTML = '
Hi
'; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); @@ -67,6 +74,7 @@ describe('dom.isVisible', function () { fixture.innerHTML = '
Hi
'; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); @@ -110,6 +118,7 @@ describe('dom.isVisible', function () { '
Hi
'; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); @@ -118,6 +127,7 @@ describe('dom.isVisible', function () { ''; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); @@ -128,6 +138,7 @@ describe('dom.isVisible', function () { '
'; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); @@ -138,6 +149,7 @@ describe('dom.isVisible', function () { '
'; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); @@ -153,6 +165,7 @@ describe('dom.isVisible', function () { fixture.innerHTML = '
Hi
'; el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); @@ -168,6 +181,7 @@ describe('dom.isVisible', function () { fixture.innerHTML = '
Hi
'; el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); @@ -182,6 +196,7 @@ describe('dom.isVisible', function () { fixture.innerHTML = '
Hi
'; el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); @@ -198,6 +213,7 @@ describe('dom.isVisible', function () { '
' + '
Hi
' + '
'; el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); @@ -213,6 +229,7 @@ describe('dom.isVisible', function () { '
' + '
Hi
' + '
'; el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isTrue(axe.commons.dom.isVisible(el)); }); @@ -228,6 +245,7 @@ describe('dom.isVisible', function () { fixture.innerHTML = '
Hi
'; el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); @@ -235,6 +253,7 @@ describe('dom.isVisible', function () { fixture.innerHTML = ''; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); @@ -242,6 +261,7 @@ describe('dom.isVisible', function () { fixture.innerHTML = '
Hello!
'; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); @@ -249,6 +269,7 @@ describe('dom.isVisible', function () { fixture.innerHTML = '
Hello!
'; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); @@ -257,6 +278,7 @@ describe('dom.isVisible', function () { '
Hello!
'; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); @@ -265,6 +287,7 @@ describe('dom.isVisible', function () { '
Hello!
'; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); @@ -342,6 +365,7 @@ describe('dom.isVisible', function () { '
Hi
'; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); @@ -352,6 +376,7 @@ describe('dom.isVisible', function () { '
'; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); @@ -403,6 +428,7 @@ describe('dom.isVisible', function () { '
StickySticky
'; var el = document.getElementById('target'); + flatTreeSetup(fixture); assert.isFalse(axe.commons.dom.isVisible(el)); }); }); diff --git a/test/integration/full/meta-refresh-no-exceptions/meta-refresh-pass.js b/test/integration/full/meta-refresh-no-exceptions/meta-refresh-pass.js index 6cd665da3..c663d3af6 100644 --- a/test/integration/full/meta-refresh-no-exceptions/meta-refresh-pass.js +++ b/test/integration/full/meta-refresh-no-exceptions/meta-refresh-pass.js @@ -3,7 +3,6 @@ describe('meta-refresh-no-exceptions pass', function () { it('should pass', function (done) { axe.run({ runOnly: 'meta-refresh-no-exceptions' }, function (err, results) { - console.log(results); try { assert.isNull(err); assert.lengthOf(results.violations, 0, 'violations'); diff --git a/test/integration/full/target-size/fixed-scroll.html b/test/integration/full/target-size/fixed-scroll.html new file mode 100644 index 000000000..0be7c89b2 --- /dev/null +++ b/test/integration/full/target-size/fixed-scroll.html @@ -0,0 +1,40 @@ + + + + Target-size position: fixed and scrolled + + + + + + + +
+ +
+

+ +

+

+ +

+
+ + + + + diff --git a/test/integration/full/target-size/fixed-scroll.js b/test/integration/full/target-size/fixed-scroll.js new file mode 100644 index 000000000..e68bcbaf8 --- /dev/null +++ b/test/integration/full/target-size/fixed-scroll.js @@ -0,0 +1,45 @@ +describe('target-size position: fixed and scrolled', () => { + 'use strict'; + let results; + + before(done => { + axe.testUtils.awaitNestedLoad(() => { + window.scrollTo(0, document.body.scrollHeight); + const options = { + runOnly: ['target-size'], + elementRef: true + }; + const context = { + // ignore the mocha links + exclude: '#mocha' + }; + axe.run(context, options, (err, r) => { + if (err) { + return done(err); + } + results = r; + done(); + }); + }); + }); + + describe('violations', function () { + it('should find 0', function () { + assert.lengthOf(results.violations, 0); + }); + }); + + describe('passes', function () { + it('should find 2', function () { + assert.lengthOf(results.passes[0].nodes, 2); + }); + }); + + it('should find 0 inapplicable', function () { + assert.lengthOf(results.inapplicable, 0); + }); + + it('should find 0 incomplete', function () { + assert.lengthOf(results.incomplete, 0); + }); +}); diff --git a/test/integration/full/target-size/target-size.js b/test/integration/full/target-size/target-size.js index 68287e6a8..42256a5fb 100644 --- a/test/integration/full/target-size/target-size.js +++ b/test/integration/full/target-size/target-size.js @@ -29,7 +29,6 @@ describe('target-size test', function () { results.passes[0].nodes.forEach(function (node) { node.element.className += ' passes'; }); - console.log(results); done(); }); }); diff --git a/test/integration/full/target-size/too-many-rects.js b/test/integration/full/target-size/too-many-rects.js index d81485024..1effc114d 100644 --- a/test/integration/full/target-size/too-many-rects.js +++ b/test/integration/full/target-size/too-many-rects.js @@ -18,7 +18,6 @@ describe('target-size too many rects test', () => { done(err); } results = r; - console.log(results); done(); }); }); diff --git a/test/integration/virtual-rules/summary-name.js b/test/integration/virtual-rules/summary-name.js index 1c711bd77..38d7ac311 100644 --- a/test/integration/virtual-rules/summary-name.js +++ b/test/integration/virtual-rules/summary-name.js @@ -26,7 +26,6 @@ describe('summary-name virtual-rule', () => { vSummary.children = []; appendSerialChild(vDetails, vSummary); const results = axe.runVirtualRule('summary-name', vSummary); - console.log(results); assert.lengthOf(results.passes, 0); assert.lengthOf(results.violations, 1); assert.lengthOf(results.incomplete, 0); From 1c15f8224a184d3c0da95942e99d9d73ad5645c0 Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:05:42 -0600 Subject: [PATCH 29/62] feat(utils): add `getElementInternals` function (#5077) Closes: https://github.com/dequelabs/axe-core/issues/5038 --- lib/core/utils/get-element-internals.js | 65 ++++++++++ lib/core/utils/index.js | 1 + test/core/utils/get-element-internals.js | 152 +++++++++++++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 lib/core/utils/get-element-internals.js create mode 100644 test/core/utils/get-element-internals.js diff --git a/lib/core/utils/get-element-internals.js b/lib/core/utils/get-element-internals.js new file mode 100644 index 000000000..7515503b0 --- /dev/null +++ b/lib/core/utils/get-element-internals.js @@ -0,0 +1,65 @@ +/** + * community protocols that axe-core supports to find element internals: + * - globalThis.axeInternalsMap.get(node) + * - node._internals (recommended) + * - node.internals + * - node.internals_ + * - node[Symbol('internals')] + * - node[Symbol('privateInternals')] + **/ +const propNames = ['_internals', 'internals', 'internals_']; +const symbolNames = ['internals', 'privateInternals']; + +/** + * Get the ElementInternals object for a custom element using a community protocol. + * @example + * // use the global map + * const internals = node.attachInternals() + * globalThis.axeInternalsMap ??= new WeakMap(); + * globalThis.axeInternalsMap.set(node, internals); + * @example + * // set property + * const internals = node.attachInternals() + * node._internals = internals; + * @param {HTMLElement} node + * @return {ElementInternals|undefined} + */ +export default function getElementInternals(node) { + // support finding internals from an optional global map. we assume that if a node is set here the value will be an ElementInternals object + const mapInternals = globalThis.axeInternalsMap?.get(node); + if (mapInternals) { + return mapInternals; + } + + // ie11 guard + if (!('ElementInternals' in window)) { + return; + } + + for (const propName of propNames) { + if (Object.getOwnPropertyDescriptor(node, propName)?.get) { + continue; + } + if (node[propName] instanceof window.ElementInternals) { + return node[propName]; + } + } + + const ownSymbols = Object.getOwnPropertySymbols(node); + if (!ownSymbols.length) { + return; + } + + for (const symbolName of symbolNames) { + const symbol = ownSymbols.find(s => s.description === symbolName); + + if (symbol) { + if (Object.getOwnPropertyDescriptor(node, symbol)?.get) { + continue; + } + if (node[symbol] instanceof window.ElementInternals) { + return node[symbol]; + } + } + } +} diff --git a/lib/core/utils/index.js b/lib/core/utils/index.js index 5f19f8b44..1edf6d278 100644 --- a/lib/core/utils/index.js +++ b/lib/core/utils/index.js @@ -42,6 +42,7 @@ export { default as getStandards } from './get-standards'; export { default as getStyleSheetFactory } from './get-stylesheet-factory'; export { default as getXpath } from './get-xpath'; export { default as getAncestry } from './get-ancestry'; +export { default as getElementInternals } from './get-element-internals'; export { default as getElementSource } from './get-element-source'; export { default as injectStyle } from './inject-style'; export { default as isArrayLike } from './is-array-like'; diff --git a/test/core/utils/get-element-internals.js b/test/core/utils/get-element-internals.js new file mode 100644 index 000000000..d87502d21 --- /dev/null +++ b/test/core/utils/get-element-internals.js @@ -0,0 +1,152 @@ +describe('utils.getElementInternals', () => { + const getElementInternals = axe.utils.getElementInternals; + let origElementInternals; + + before(() => { + origElementInternals = window.ElementInternals; + customElements.define( + 'utils-get-element-internals', + class CustomButton extends HTMLElement { + constructor() { + super(); + } + } + ); + }); + + afterEach(() => { + delete globalThis.axeInternalsMap; + window.ElementInternals = origElementInternals; + }); + + it('returns undefined for element without internals', () => { + const node = document.createElement('utils-get-element-internals'); + + assert.isUndefined(getElementInternals(node)); + }); + + for (const prop of ['_internals', 'internals', 'internals_']) { + it(`returns internals from prop "${prop}"`, () => { + const node = document.createElement('utils-get-element-internals'); + const internals = node.attachInternals(); + node[prop] = internals; + + assert.strictEqual(getElementInternals(node), internals); + }); + + it(`should not return internals from prop "${prop}" getter`, () => { + const node = document.createElement('utils-get-element-internals'); + const internals = node.attachInternals(); + Object.defineProperty(node, prop, { + get() { + return internals; + } + }); + + assert.isUndefined(getElementInternals(node)); + }); + + it(`should not return internals from prop "${prop}" if not of type ElementInternals`, () => { + const node = document.createElement('utils-get-element-internals'); + node[prop] = 'hello world'; + + assert.isUndefined(getElementInternals(node)); + }); + } + + for (const symbol of ['internals', 'privateInternals']) { + it(`returns internals from "Symbol('${symbol}')"`, () => { + const node = document.createElement('utils-get-element-internals'); + const internals = node.attachInternals(); + node[Symbol(symbol)] = internals; + + assert.strictEqual(getElementInternals(node), internals); + }); + + it(`should not return internals from "Symbol('${symbol}')" getter`, () => { + const node = document.createElement('utils-get-element-internals'); + const internals = node.attachInternals(); + Object.defineProperty(node, Symbol(symbol), { + get() { + return internals; + } + }); + + assert.isUndefined(getElementInternals(node)); + }); + + it(`should not return internals from prop "Symbol('${symbol}')" if not of type ElementInternals`, () => { + const node = document.createElement('utils-get-element-internals'); + node[Symbol(symbol)] = 'hello world'; + + assert.isUndefined(getElementInternals(node)); + }); + } + + it('returns internals from global map', () => { + const node = document.createElement('utils-get-element-internals'); + const internals = node.attachInternals(); + globalThis.axeInternalsMap = new WeakMap(); + globalThis.axeInternalsMap.set(node, internals); + + assert.strictEqual(getElementInternals(node), internals); + }); + + it(`uses props if element doesn't exist in the global map`, () => { + const node = document.createElement('utils-get-element-internals'); + const internals = node.attachInternals(); + node._internals = internals; + globalThis.axeInternalsMap = new WeakMap(); + + assert.strictEqual(getElementInternals(node), internals); + }); + + it('uses global map over props', () => { + const node = document.createElement('utils-get-element-internals'); + const internals = node.attachInternals(); + globalThis.axeInternalsMap = new WeakMap(); + globalThis.axeInternalsMap.set(node, internals); + + // can't attach internals twice so we'll create a fake internals object that is still an instanceof ElementInternals + node._internals = {}; + Object.setPrototypeOf(node._internals, ElementInternals.prototype); + + assert.isTrue(node._internals instanceof ElementInternals); + assert.strictEqual(getElementInternals(node), internals); + }); + + it('gets internals when getter and another prop is set', () => { + const node = document.createElement('utils-get-element-internals'); + const internals = node.attachInternals(); + Object.defineProperty(node, '_internals', { + get() { + return internals; + } + }); + node.internals = internals; + + assert.strictEqual(getElementInternals(node), internals); + }); + + it('gets internals when getter and another symbol is set', () => { + const node = document.createElement('utils-get-element-internals'); + const internals = node.attachInternals(); + Object.defineProperty(node, Symbol('internals'), { + get() { + return internals; + } + }); + node[Symbol('privateInternals')] = internals; + + assert.strictEqual(getElementInternals(node), internals); + }); + + it('guards when window.ElementInternals does not exist', () => { + delete window.ElementInternals; + const node = document.createElement('utils-get-element-internals'); + const internals = node.attachInternals(); + node._internals = internals; + + assert.isUndefined(getElementInternals(node)); + }); +}); From d1fabaad99b1b055b2436a0c3efc22cb66df3934 Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:06:23 -0600 Subject: [PATCH 30/62] fix(utils/getAncestry): escape node name (#5079) Closes: #5078 --- lib/core/utils/get-ancestry.js | 3 ++- test/core/utils/get-ancestry.js | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/core/utils/get-ancestry.js b/lib/core/utils/get-ancestry.js index 5652c69ac..c5b1fd2d5 100644 --- a/lib/core/utils/get-ancestry.js +++ b/lib/core/utils/get-ancestry.js @@ -1,7 +1,8 @@ +import escapeSelector from './escape-selector'; import getShadowSelector from './get-shadow-selector'; function generateAncestry(node) { - const nodeName = node.nodeName.toLowerCase(); + const nodeName = escapeSelector(node.nodeName.toLowerCase()); const parentElement = node.parentElement; const parentNode = node.parentNode; diff --git a/test/core/utils/get-ancestry.js b/test/core/utils/get-ancestry.js index 8f570e907..bfd419d96 100644 --- a/test/core/utils/get-ancestry.js +++ b/test/core/utils/get-ancestry.js @@ -71,4 +71,31 @@ describe('axe.utils.getAncestry', () => { 'div:nth-child(2)' ]); }); + + it('escapes the node name', () => { + fixture.innerHTML = ` +
+ + + +
+ + + + `; + + const sel1 = axe.utils.getAncestry(document.querySelector('#target1')); + assert.equal( + sel1, + 'html > body > div:nth-child(1) > div:nth-child(1) > hello\\=\\"world\\" > button' + ); + assert.isNotNull(document.querySelector(sel1)); + + const sel2 = axe.utils.getAncestry(document.querySelector('#target2')); + assert.equal( + sel2, + 'html > body > div:nth-child(1) > emoji-👍:nth-child(2) > button' + ); + assert.isNotNull(document.querySelector(sel2)); + }); }); From 3e49812eedede3ac941b3519fb2a08770da960d3 Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:11:00 -0600 Subject: [PATCH 31/62] chore: fix cherry-pick script buffer size error for large git logs (#5071) This fixes the cherry-pick script's problem of trying to read git log information now that we have too many for the current buffer size. Originally done in https://github.com/dequelabs/axe-core/pull/5048 but porting to it's own pr since that one was closed. --- build/cherry-pick.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build/cherry-pick.js b/build/cherry-pick.js index f8040807b..c5575f364 100755 --- a/build/cherry-pick.js +++ b/build/cherry-pick.js @@ -5,6 +5,9 @@ const conventionalCommitsParser = require('conventional-commits-parser'); const chalk = require('chalk'); const { version } = require('../package.json'); +// Node's default execSync buffer (~1 MiB) is too small for `git log` on long histories. +const GIT_LOG_EXEC_OPTS = { maxBuffer: 50 * 1024 * 1024 }; + const releaseType = process.argv[2]; let ignoreCommits = process.argv[3]; @@ -50,7 +53,8 @@ function getCommits(branch) { // all commits are too large for execSync buffer size so we'll just get since the last 3 years const date = new Date(new Date().setFullYear(new Date().getFullYear() - 3)); const stdout = execSync( - `git log ${branch || ''} --abbrev-commit --since=${date.getFullYear()}` + `git log ${branch || ''} --no-merges --abbrev-commit --since=${date.getFullYear()}`, + GIT_LOG_EXEC_OPTS ).toString(); const allCommits = stdout .split(/commit (?=[\w\d]{8}[\n\r])/) From ea7202c6bf1a6166c878dbf19bb5454372b61fae Mon Sep 17 00:00:00 2001 From: yugo innami <58389827+nami8824@users.noreply.github.com> Date: Fri, 24 Apr 2026 02:22:19 +0900 Subject: [PATCH 32/62] fix(commons/text): exclude natively hidden elements from aria-labelledby accessible name (#5076) Fixes accessible name calculation to correctly exclude `' + - '

Content

' - ); + describe('with pseudo elements', () => { + it('should return undefined if :before pseudo element has a background color', () => { + const params = checkSetup(html` + +
+

Content

+
+ `); assert.isUndefined(contrastEvaluate.apply(checkContext, params)); assert.deepEqual(checkContext._data, { @@ -484,11 +613,24 @@ describe('color-contrast', function () { ); }); - it('should return undefined if :after pseudo element has a background color', function () { - var params = checkSetup( - '' + - '

Content

' - ); + it('should return undefined if :after pseudo element has a background color', () => { + const params = checkSetup(html` + +
+

Content

+
+ `); assert.isUndefined(contrastEvaluate.apply(checkContext, params)); assert.deepEqual(checkContext._data, { @@ -503,18 +645,23 @@ describe('color-contrast', function () { ); }); - it('should return undefined if pseudo element has a background image', function () { - var dataURI = - 'data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/' + - 'XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkA' + - 'ABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKU' + - 'E1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7'; - - var params = checkSetup( - '' + - '

Content

' + it('should return undefined if pseudo element has a background image', () => { + const params = checkSetup( + html` +
+

Content

+
` ); assert.isUndefined(contrastEvaluate.apply(checkContext, params)); @@ -530,169 +677,286 @@ describe('color-contrast', function () { ); }); - it('should not return undefined if pseudo element has no content', function () { - var params = checkSetup( - '' + - '

Content

' - ); + it('should not return undefined if pseudo element has no content', () => { + const params = checkSetup(html` + +
+

Content

+
+ `); assert.isTrue(contrastEvaluate.apply(checkContext, params)); }); - it('should not return undefined if pseudo element is not absolutely positioned no content', function () { - var params = checkSetup( - '' + - '

Content

' - ); + it('should not return undefined if pseudo element is not absolutely positioned no content', () => { + const params = checkSetup(html` + +
+

Content

+
+ `); assert.isTrue(contrastEvaluate.apply(checkContext, params)); }); - it('should not return undefined if pseudo element is has zero dimension', function () { - var params = checkSetup( - '' + - '

Content

' - ); + it('should not return undefined if pseudo element is has zero dimension', () => { + const params = checkSetup(html` + +
+

Content

+
+ `); assert.isTrue(contrastEvaluate.apply(checkContext, params)); }); - it("should not return undefined if pseudo element doesn't have a background", function () { - var params = checkSetup( - '' + - '

Content

' - ); + it("should not return undefined if pseudo element doesn't have a background", () => { + const params = checkSetup(html` + +
+

Content

+
+ `); assert.isTrue(contrastEvaluate.apply(checkContext, params)); }); - it('should not return undefined if pseudo element has visibility: hidden', function () { - var params = checkSetup( - '' + - '

Content

' - ); + it('should not return undefined if pseudo element has visibility: hidden', () => { + const params = checkSetup(html` + +
+

Content

+
+ `); assert.isTrue(contrastEvaluate.apply(checkContext, params)); }); - it('should not return undefined if pseudo element has display: none', function () { - var params = checkSetup( - '' + - '

Content

' - ); + it('should not return undefined if pseudo element has display: none', () => { + const params = checkSetup(html` + +
+

Content

+
+ `); assert.isTrue(contrastEvaluate.apply(checkContext, params)); }); - it('should return undefined if pseudo element is more than 25% of the element', function () { - var params = checkSetup( - '' + - '

Content

' - ); + it('should return undefined if pseudo element is more than 25% of the element', () => { + const params = checkSetup(html` + +

Content

+ `); assert.isUndefined(contrastEvaluate.apply(checkContext, params)); }); - it('should not return undefined if pseudo element is 25% of the element', function () { - var params = checkSetup( - '' + - '

Content

' - ); + it('should not return undefined if pseudo element is 25% of the element', () => { + const params = checkSetup(html` + +

Content

+ `); assert.isTrue(contrastEvaluate.apply(checkContext, params)); }); }); - describe('with special texts', function () { - it('should return undefined for a single character text with insufficient contrast', function () { - var params = checkSetup( - '
' + - '
X
' + - '
' - ); + describe('with special texts', () => { + it('should return undefined for a single character text with insufficient contrast', () => { + const params = checkSetup(html` +
+
X
+
+ `); - var actual = contrastEvaluate.apply(checkContext, params); + const actual = contrastEvaluate.apply(checkContext, params); assert.isUndefined(actual); assert.equal(checkContext._data.messageKey, 'shortTextContent'); }); - it('should return true for a single character text with insufficient contrast', function () { - var params = checkSetup( - '
' + - '
X
' + - '
' - ); + it('should return true for a single character text with insufficient contrast', () => { + const params = checkSetup(html` +
+
X
+
+ `); - var actual = contrastEvaluate.apply(checkContext, params); + const actual = contrastEvaluate.apply(checkContext, params); assert.isUndefined(actual); assert.equal(checkContext._data.messageKey, 'shortTextContent'); }); - it('should return undefined when the text only contains nonBmp unicode when the ignoreUnicode option is true', function () { - var params = checkSetup( - '
' + - '
₠ ₡ ₢ ₣
' + - '
', + it('should return undefined when the text only contains nonBmp unicode when the ignoreUnicode option is true', () => { + const params = checkSetup( + html` +
+
+ ₠ ₡ ₢ ₣ +
+
+ `, { ignoreUnicode: true } ); - var actual = contrastEvaluate.apply(checkContext, params); + const actual = contrastEvaluate.apply(checkContext, params); assert.isUndefined(actual); assert.equal(checkContext._data.messageKey, 'nonBmp'); }); - it('should return true when the text only contains nonBmp unicode when the ignoreUnicode option is false, and there is sufficient contrast', function () { - var params = checkSetup( - '
' + - '
' + - '
', + it('should return true when the text only contains nonBmp unicode when the ignoreUnicode option is false, and there is sufficient contrast', () => { + const params = checkSetup( + html` +
+
+
+ `, { ignoreUnicode: false } ); - var actual = contrastEvaluate.apply(checkContext, params); + const actual = contrastEvaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('should return undefined when the text only contains nonBmp unicode when the ignoreUnicode option is false and the ignoreLength option is default, and there is insufficient contrast', function () { - var params = checkSetup( - '
' + - '
' + - '
', + it('should return undefined when the text only contains nonBmp unicode when the ignoreUnicode option is false and the ignoreLength option is default, and there is insufficient contrast', () => { + const params = checkSetup( + html` +
+
+
+ `, { ignoreUnicode: false } ); - var actual = contrastEvaluate.apply(checkContext, params); + const actual = contrastEvaluate.apply(checkContext, params); assert.isUndefined(actual); assert.equal(checkContext._data.messageKey, 'shortTextContent'); }); - it('should return false when the text only contains nonBmp unicode when the ignoreUnicode option is false and the ignoreLength option is true, and there is insufficient contrast', function () { - var params = checkSetup( - '
' + - '
' + - '
', + it('should return false when the text only contains nonBmp unicode when the ignoreUnicode option is false and the ignoreLength option is true, and there is insufficient contrast', () => { + const params = checkSetup( + html` +
+
+
+ `, { ignoreUnicode: false, ignoreLength: true } ); - var actual = contrastEvaluate.apply(checkContext, params); + const actual = contrastEvaluate.apply(checkContext, params); assert.isFalse(actual); }); }); - describe('options', function () { - it('should support options.boldValue', function () { - var params = checkSetup( - '
' + - 'My text
', + describe('options', () => { + it('should support options.boldValue', () => { + const params = checkSetup( + html` +
+ My text +
+ `, { boldValue: 100 } @@ -702,10 +966,16 @@ describe('color-contrast', function () { assert.deepEqual(checkContext._relatedNodes, []); }); - it('should support options.boldTextPt', function () { - var params = checkSetup( - '
' + - 'My text
', + it('should support options.boldTextPt', () => { + const params = checkSetup( + html` +
+ My text +
+ `, { boldTextPt: 6 } @@ -715,10 +985,16 @@ describe('color-contrast', function () { assert.deepEqual(checkContext._relatedNodes, []); }); - it('should support options.largeTextPt', function () { - var params = checkSetup( - '
' + - 'My text
', + it('should support options.largeTextPt', () => { + const params = checkSetup( + html` +
+ My text +
+ `, { largeTextPt: 6 } @@ -728,10 +1004,16 @@ describe('color-contrast', function () { assert.deepEqual(checkContext._relatedNodes, []); }); - it('should support options.contrastRatio.normal.expected', function () { - var params = checkSetup( - '
' + - 'My text
', + it('should support options.contrastRatio.normal.expected', () => { + const params = checkSetup( + html` +
+ My text +
+ `, { contrastRatio: { normal: { @@ -745,10 +1027,16 @@ describe('color-contrast', function () { assert.deepEqual(checkContext._relatedNodes, []); }); - it('should support options.contrastRatio.normal.minThreshold', function () { - var params = checkSetup( - '
' + - 'My text
', + it('should support options.contrastRatio.normal.minThreshold', () => { + const params = checkSetup( + html` +
+ My text +
+ `, { contrastRatio: { normal: { @@ -762,10 +1050,12 @@ describe('color-contrast', function () { assert.deepEqual(checkContext._relatedNodes, []); }); - it('should not report incomplete when options.contrastRatio.normal.minThreshold is set', function () { - var params = checkSetup( - ` -

+ it('should not report incomplete when options.contrastRatio.normal.minThreshold is set', () => { + const params = checkSetup( + html`

Some text in English

`, { @@ -781,10 +1071,16 @@ describe('color-contrast', function () { assert.isUndefined(checkContext._data.messageKey); }); - it('should support options.contrastRatio.normal.maxThreshold', function () { - var params = checkSetup( - '
' + - 'My text
', + it('should support options.contrastRatio.normal.maxThreshold', () => { + const params = checkSetup( + html` +
+ My text +
+ `, { contrastRatio: { normal: { @@ -798,10 +1094,12 @@ describe('color-contrast', function () { assert.deepEqual(checkContext._relatedNodes, []); }); - it('should not report incomplete when options.contrastRatio.normal.maxThreshold is set', function () { - var params = checkSetup( - ` -

+ it('should not report incomplete when options.contrastRatio.normal.maxThreshold is set', () => { + const params = checkSetup( + html`

Some text in English

`, { @@ -817,10 +1115,16 @@ describe('color-contrast', function () { assert.isUndefined(checkContext._data.messageKey); }); - it('should support options.contrastRatio.large.expected', function () { - var params = checkSetup( - '
' + - 'My text
', + it('should support options.contrastRatio.large.expected', () => { + const params = checkSetup( + html` +
+ My text +
+ `, { contrastRatio: { large: { @@ -834,10 +1138,16 @@ describe('color-contrast', function () { assert.deepEqual(checkContext._relatedNodes, []); }); - it('should support options.contrastRatio.large.minThreshold', function () { - var params = checkSetup( - '
' + - 'My text
', + it('should support options.contrastRatio.large.minThreshold', () => { + const params = checkSetup( + html` +
+ My text +
+ `, { contrastRatio: { large: { @@ -851,10 +1161,12 @@ describe('color-contrast', function () { assert.deepEqual(checkContext._relatedNodes, []); }); - it('should not report incomplete when options.contrastRatio.large.minThreshold is set', function () { - var params = checkSetup( - ` -

+ it('should not report incomplete when options.contrastRatio.large.minThreshold is set', () => { + const params = checkSetup( + html`

Some text in English

`, { @@ -870,10 +1182,16 @@ describe('color-contrast', function () { assert.isUndefined(checkContext._data.messageKey); }); - it('should support options.contrastRatio.large.maxThreshold', function () { - var params = checkSetup( - '
' + - 'My text
', + it('should support options.contrastRatio.large.maxThreshold', () => { + const params = checkSetup( + html` +
+ My text +
+ `, { contrastRatio: { large: { @@ -887,10 +1205,12 @@ describe('color-contrast', function () { assert.deepEqual(checkContext._relatedNodes, []); }); - it('should not report incomplete when options.contrastRatio.large.maxThreshold is set', function () { - var params = checkSetup( - ` -

+ it('should not report incomplete when options.contrastRatio.large.maxThreshold is set', () => { + const params = checkSetup( + html`

Some text in English

`, { @@ -906,10 +1226,25 @@ describe('color-contrast', function () { assert.isUndefined(checkContext._data.messageKey); }); - it('should ignore pseudo element with options.ignorePseudo', function () { - var params = checkSetup( - '' + - '

Content

', + it('should ignore pseudo element with options.ignorePseudo', () => { + const params = checkSetup( + html` + +
+

Content

+
+ `, { ignorePseudo: true } @@ -918,11 +1253,25 @@ describe('color-contrast', function () { assert.isTrue(contrastEvaluate.apply(checkContext, params)); }); - it('should adjust the pseudo element minimum size with the options.pseudoSizeThreshold', function () { - var params = checkSetup( - '' + - '

Content

', + it('should adjust the pseudo element minimum size with the options.pseudoSizeThreshold', () => { + const params = checkSetup( + html` + +

Content

+ `, { pseudoSizeThreshold: 0.2 } @@ -931,21 +1280,18 @@ describe('color-contrast', function () { }); }); - (shadowSupported ? it : xit)( - 'returns colors across Shadow DOM boundaries', - function () { - var params = shadowCheckSetup( - '
', - '

Text

' - ); - var container = fixture.querySelector('#container'); - var result = contrastEvaluate.apply(checkContext, params); - assert.isFalse(result); - assert.deepEqual(checkContext._relatedNodes, [container]); - } - ); - - (shadowSupported ? it : xit)('handles elements', () => { + it('returns colors across Shadow DOM boundaries', () => { + const params = shadowCheckSetup( + '
', + '

Text

' + ); + const container = fixture.querySelector('#container'); + const result = contrastEvaluate.apply(checkContext, params); + assert.isFalse(result); + assert.deepEqual(checkContext._relatedNodes, [container]); + }); + + it('handles elements', () => { fixture.innerHTML = '

Slotted text

'; const container = fixture.querySelector('#container'); @@ -968,26 +1314,32 @@ describe('color-contrast', function () { assert.deepEqual(checkContext._relatedNodes, [shadowContainer]); }); - describe('with text-shadow', function () { - it('passes if thin text shadows have sufficient contrast with the text', function () { - var params = checkSetup( - '
' + - ' Hello world' + - '
' - ); + describe('with text-shadow', () => { + it('passes if thin text shadows have sufficient contrast with the text', () => { + const params = checkSetup(html` +
+ Hello world +
+ `); assert.isTrue(contrastEvaluate.apply(checkContext, params)); }); - it('does not count text shadows of offset 0, blur 0 as part of the background color', function () { - var params = checkSetup( - '
' + - ' Hello world' + - '
' - ); + it('does not count text shadows of offset 0, blur 0 as part of the background color', () => { + const params = checkSetup(html` +
+ Hello world +
+ `); - var white = new axe.commons.color.Color(255, 255, 255, 1); + const white = new axe.commons.color.Color(255, 255, 255, 1); assert.isTrue(contrastEvaluate.apply(checkContext, params)); assert.equal(checkContext._data.bgColor, white.toHexString()); @@ -995,78 +1347,103 @@ describe('color-contrast', function () { assert.equal(checkContext._data.contrastRatio, '4.84'); }); - it('passes if thin text shadows have sufficient contrast with the background', function () { - var params = checkSetup( - '
' + - ' Hello world' + - '
' - ); + it('passes if thin text shadows have sufficient contrast with the background', () => { + const params = checkSetup(html` +
+ Hello world +
+ `); assert.isTrue(contrastEvaluate.apply(checkContext, params)); }); - it('fails if text shadows have sufficient contrast with the background if its width is thicker than `shadowOutlineEmMax`', function () { - var checkOptions = { shadowOutlineEmMax: 0.05 }; - var params = checkSetup( - '
' + - ' Hello world' + - '
', + it('fails if text shadows have sufficient contrast with the background if its width is thicker than `shadowOutlineEmMax`', () => { + const checkOptions = { shadowOutlineEmMax: 0.05 }; + const params = checkSetup( + html` +
+ Hello world +
+ `, checkOptions ); assert.isFalse(contrastEvaluate.apply(checkContext, params)); assert.equal(checkContext._data.messageKey, null); }); - it('fails if text shadows do not have sufficient contrast with the foreground', function () { - var params = checkSetup( - '
' + - ' Hello world' + - '
' - ); + it('fails if text shadows do not have sufficient contrast with the foreground', () => { + const params = checkSetup(html` +
+ Hello world +
+ `); assert.isFalse(contrastEvaluate.apply(checkContext, params)); assert.isNull(checkContext._data.messageKey); }); - it('fails if text shadows do not have sufficient contrast with the background', function () { - var params = checkSetup( - '
' + - ' Hello world' + - '
' - ); + it('fails if text shadows do not have sufficient contrast with the background', () => { + const params = checkSetup(html` +
+ Hello world +
+ `); assert.isFalse(contrastEvaluate.apply(checkContext, params)); assert.equal(checkContext._data.messageKey, 'shadowOnBgColor'); }); - it("fails if thick text shadows don't have sufficient contrast", function () { - var params = checkSetup( - '
' + - ' Hello world' + - '
' - ); + it("fails if thick text shadows don't have sufficient contrast", () => { + const params = checkSetup(html` +
+ Hello world +
+ `); assert.isTrue(contrastEvaluate.apply(checkContext, params)); }); - it("passes if thin text shadows don't have sufficient contrast, but the text and background do", function () { - var params = checkSetup( - '
' + - ' Hello world' + - '
' - ); + it("passes if thin text shadows don't have sufficient contrast, but the text and background do", () => { + const params = checkSetup(html` +
+ Hello world +
+ `); assert.isTrue(contrastEvaluate.apply(checkContext, params)); }); - it('incompletes if text-shadow is only on part of the text', function () { - var params = checkSetup(` -
Hello world
+ it('incompletes if text-shadow is only on part of the text', () => { + const params = checkSetup(html` +
+ Hello world +
`); assert.isUndefined(contrastEvaluate.apply(checkContext, params)); diff --git a/test/checks/color/link-in-text-block-style.js b/test/checks/color/link-in-text-block-style.js index cc8e3d812..4113d3228 100644 --- a/test/checks/color/link-in-text-block-style.js +++ b/test/checks/color/link-in-text-block-style.js @@ -1,6 +1,6 @@ describe('link-in-text-block-style', () => { + const html = axe.testUtils.html; const fixture = document.getElementById('fixture'); - const shadowSupport = axe.testUtils.shadowSupport; let styleElm; const checkContext = axe.testUtils.MockCheckContext(); @@ -58,35 +58,33 @@ describe('link-in-text-block-style', () => { if (!name) { return propPiece; } else { - return name + '-' + propPiece.toLowerCase(); + return `${name}-${propPiece.toLowerCase()}`; } }, null); // Return indented line of style code - return ' ' + cssPropName + ':' + styleObj[prop] + ';'; + return ` ${cssPropName}:${styleObj[prop]};`; }) .join('\n'); // Add to the style element - styleElm.innerHTML += selector + ' {\n' + cssLines + '\n}\n'; + styleElm.innerHTML += `${selector} { +${cssLines} +} +`; } function getLinkElm(linkStyle) { // Get a random id and build the style strings - const linkId = 'linkid-' + Math.floor(Math.random() * 100000); - const parId = 'parid-' + Math.floor(Math.random() * 100000); - - createStyleString('#' + linkId, linkStyle); - createStyleString('#' + parId, {}); - - fixture.innerHTML += - '

Text ' + - 'link' + - '

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

Text + link +

`; axe.testUtils.flatTreeSetup(fixture); return document.getElementById(linkId); } @@ -99,12 +97,13 @@ describe('link-in-text-block-style', () => { }); it('passes the selected node and closest ancestral block element', () => { - fixture.innerHTML = - '
' + - '

' + - ' link text ' + - ' inside block

inside block' + - '
outside block
'; + fixture.innerHTML = html` +
+

+ link text + inside block

inside block +
outside block
+ `; axe.testUtils.flatTreeSetup(fixture); const linkElm = document.getElementById('link'); @@ -112,39 +111,32 @@ describe('link-in-text-block-style', () => { assert.isFalse(linkInBlockStyleCheck.call(checkContext, linkElm)); }); - (shadowSupport.v1 ? it : xit)( - 'works with the block outside the shadow tree', - () => { - const parentElm = document.createElement('div'); - const shadow = parentElm.attachShadow({ mode: 'open' }); - shadow.innerHTML = - 'Link'; - const linkElm = shadow.querySelector('a'); - fixture.appendChild(parentElm); + it('works with the block outside the shadow tree', () => { + const parentElm = document.createElement('div'); + const shadow = parentElm.attachShadow({ mode: 'open' }); + shadow.innerHTML = + 'Link'; + const linkElm = shadow.querySelector('a'); + fixture.appendChild(parentElm); - axe.testUtils.flatTreeSetup(fixture); + axe.testUtils.flatTreeSetup(fixture); - assert.isTrue(linkInBlockStyleCheck.call(checkContext, linkElm)); - } - ); - - (shadowSupport.v1 ? it : xit)( - 'works with the link inside the shadow tree slot', - () => { - const div = document.createElement('div'); - div.setAttribute('style', 'text-decoration:none;'); - div.innerHTML = - 'Link'; - const shadow = div.attachShadow({ mode: 'open' }); - shadow.innerHTML = '

'; - fixture.appendChild(div); - - axe.testUtils.flatTreeSetup(fixture); - const linkElm = div.querySelector('a'); - - assert.isTrue(linkInBlockStyleCheck.call(checkContext, linkElm)); - } - ); + assert.isTrue(linkInBlockStyleCheck.call(checkContext, linkElm)); + }); + + it('works with the link inside the shadow tree slot', () => { + const div = document.createElement('div'); + div.setAttribute('style', 'text-decoration:none;'); + div.innerHTML = 'Link'; + const shadow = div.attachShadow({ mode: 'open' }); + shadow.innerHTML = '

'; + fixture.appendChild(div); + + axe.testUtils.flatTreeSetup(fixture); + const linkElm = div.querySelector('a'); + + assert.isTrue(linkInBlockStyleCheck.call(checkContext, linkElm)); + }); }); describe('links distinguished through style', () => { @@ -165,10 +157,14 @@ describe('link-in-text-block-style', () => { }); it('returns undefined when the link has a :before pseudo element', () => { - const link = queryFixture(` + const link = queryFixture(html`

A link inside a block of text

`).actualNode; @@ -179,10 +175,14 @@ describe('link-in-text-block-style', () => { }); it('returns undefined when the link has a :after pseudo element', () => { - const link = queryFixture(` + const link = queryFixture(html`

A link inside a block of text

`).actualNode; @@ -193,10 +193,15 @@ describe('link-in-text-block-style', () => { }); it('does not return undefined when the pseudo element content is none', () => { - const link = queryFixture(` + const link = queryFixture(html`

A link inside a block of text

`).actualNode; diff --git a/test/checks/color/link-in-text-block.js b/test/checks/color/link-in-text-block.js index d8d46e0be..7a8569dfd 100644 --- a/test/checks/color/link-in-text-block.js +++ b/test/checks/color/link-in-text-block.js @@ -1,6 +1,6 @@ describe('link-in-text-block', () => { + const html = axe.testUtils.html; const fixture = document.getElementById('fixture'); - const shadowSupport = axe.testUtils.shadowSupport; let styleElm; const checkContext = axe.testUtils.MockCheckContext(); @@ -53,35 +53,33 @@ describe('link-in-text-block', () => { if (!name) { return propPiece; } else { - return name + '-' + propPiece.toLowerCase(); + return `${name}-${propPiece.toLowerCase()}`; } }, null); // Return indented line of style code - return ' ' + cssPropName + ':' + styleObj[prop] + ';'; + return ` ${cssPropName}:${styleObj[prop]};`; }) .join('\n'); // Add to the style element - styleElm.innerHTML += selector + ' {\n' + cssLines + '\n}\n'; + styleElm.innerHTML += `${selector} { +${cssLines} +} +`; } function getLinkElm(linkStyle, paragraphStyle) { // Get a random id and build the style strings - const linkId = 'linkid-' + Math.floor(Math.random() * 100000); - const parId = 'parid-' + Math.floor(Math.random() * 100000); - - createStyleString('#' + linkId, linkStyle); - createStyleString('#' + parId, paragraphStyle); - - fixture.innerHTML += - '

Text ' + - 'link' + - '

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

Text + link +

`; axe.testUtils.flatTreeSetup(fixture); return document.getElementById(linkId); } @@ -94,12 +92,17 @@ describe('link-in-text-block', () => { }); it('passes the selected node and closest ancestral block element', () => { - fixture.innerHTML = - '
' + - '

' + - ' link text ' + - ' inside block

inside block' + - '
outside block
'; + fixture.innerHTML = html` +
+ +

+ link text inside block +

+ inside block +
+ outside block +
+ `; axe.testUtils.flatTreeSetup(fixture); const linkElm = document.getElementById('link'); @@ -112,53 +115,44 @@ describe('link-in-text-block', () => { assert.equal(checkContext._data.messageKey, 'fgContrast'); }); - (shadowSupport.v1 ? it : xit)( - 'works with the block outside the shadow tree', - () => { - const parentElm = document.createElement('div'); - parentElm.setAttribute( - 'style', - 'color:#100; background-color:#FFFFFF;' - ); - const shadow = parentElm.attachShadow({ mode: 'open' }); - shadow.innerHTML = - 'Link'; - const linkElm = shadow.querySelector('a'); - fixture.appendChild(parentElm); + it('works with the block outside the shadow tree', () => { + const parentElm = document.createElement('div'); + parentElm.setAttribute('style', 'color:#100; background-color:#FFFFFF;'); + const shadow = parentElm.attachShadow({ mode: 'open' }); + shadow.innerHTML = + 'Link'; + const linkElm = shadow.querySelector('a'); + fixture.appendChild(parentElm); - axe.testUtils.flatTreeSetup(fixture); + axe.testUtils.flatTreeSetup(fixture); - assert.isFalse( - axe.testUtils - .getCheckEvaluate('link-in-text-block') - .call(checkContext, linkElm) - ); - assert.equal(checkContext._data.messageKey, 'fgContrast'); - } - ); - - (shadowSupport.v1 ? it : xit)( - 'works with the link inside the shadow tree slot', - () => { - const div = document.createElement('div'); - div.setAttribute('style', 'color:#100; background-color:#FFFFFF;'); - div.innerHTML = - 'Link'; - const shadow = div.attachShadow({ mode: 'open' }); - shadow.innerHTML = '

'; - fixture.appendChild(div); - - axe.testUtils.flatTreeSetup(fixture); - const linkElm = div.querySelector('a'); + assert.isFalse( + axe.testUtils + .getCheckEvaluate('link-in-text-block') + .call(checkContext, linkElm) + ); + assert.equal(checkContext._data.messageKey, 'fgContrast'); + }); - assert.isFalse( - axe.testUtils - .getCheckEvaluate('link-in-text-block') - .call(checkContext, linkElm) - ); - assert.equal(checkContext._data.messageKey, 'fgContrast'); - } - ); + it('works with the link inside the shadow tree slot', () => { + const div = document.createElement('div'); + div.setAttribute('style', 'color:#100; background-color:#FFFFFF;'); + div.innerHTML = + 'Link'; + const shadow = div.attachShadow({ mode: 'open' }); + shadow.innerHTML = '

'; + fixture.appendChild(div); + + axe.testUtils.flatTreeSetup(fixture); + const linkElm = div.querySelector('a'); + + assert.isFalse( + axe.testUtils + .getCheckEvaluate('link-in-text-block') + .call(checkContext, linkElm) + ); + assert.equal(checkContext._data.messageKey, 'fgContrast'); + }); }); describe('links distinguished through color', () => { @@ -284,12 +278,18 @@ describe('link-in-text-block', () => { }); it('should return the proper values stored in data (fgContrast)', () => { - fixture.innerHTML = - '
' + - '

' + - ' link text ' + - ' inside block

inside block' + - '
outside block
'; + fixture.innerHTML = html` +
+ +

+ link text inside + block +

+ inside block +
+ outside block +
+ `; axe.testUtils.flatTreeSetup(fixture); const linkElm = document.getElementById('link'); diff --git a/test/checks/forms/autocomplete-appropriate.js b/test/checks/forms/autocomplete-appropriate.js index 96fd80255..5df0ab43c 100644 --- a/test/checks/forms/autocomplete-appropriate.js +++ b/test/checks/forms/autocomplete-appropriate.js @@ -1,98 +1,100 @@ -describe('autocomplete-appropriate', function () { - 'use strict'; +describe('autocomplete-appropriate', () => { + const fixture = document.getElementById('fixture'); + const checkSetup = axe.testUtils.checkSetup; + const checkContext = axe.testUtils.MockCheckContext(); + const evaluate = axe.testUtils.getCheckEvaluate('autocomplete-appropriate'); - var fixture = document.getElementById('fixture'); - var checkSetup = axe.testUtils.checkSetup; - var checkContext = axe.testUtils.MockCheckContext(); - var evaluate = axe.testUtils.getCheckEvaluate('autocomplete-appropriate'); - - beforeEach(function () { + beforeEach(() => { axe._tree = undefined; }); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; checkContext.reset(); }); function autocompleteCheckParams(term, type, options) { return checkSetup( - '', + ``, options ); } - it('returns true for non-select elements', function () { - ['div', 'button', 'select', 'textarea'].forEach(function (tagName) { - var elm = document.createElement(tagName); + it('returns true for non-select elements', () => { + ['div', 'button', 'select', 'textarea'].forEach(tagName => { + const elm = document.createElement(tagName); elm.setAttribute('autocomplete', 'foo'); elm.setAttribute('type', 'email'); - var params = checkSetup(elm); + const params = checkSetup(elm); assert.isTrue( evaluate.apply(checkContext, params), - 'failed for ' + tagName + `failed for ${tagName}` ); }); }); - it('returns true if the input type is in the map', function () { - var options = { foo: ['url'] }; - var params = autocompleteCheckParams('foo', 'url', options); + it('returns true if the input type is in the map', () => { + const options = { foo: ['url'] }; + const params = autocompleteCheckParams('foo', 'url', options); assert.isTrue(evaluate.apply(checkContext, params)); }); - it('returns false if the input type is not in the map', function () { - var options = { foo: ['url'] }; - var params = autocompleteCheckParams('foo', 'email', options); + it('returns false if the input type is not in the map', () => { + const options = { foo: ['url'] }; + const params = autocompleteCheckParams('foo', 'email', options); assert.isFalse(evaluate.apply(checkContext, params)); }); - it('returns true if the input type is text and the term is undefined', function () { - var options = {}; - var params = autocompleteCheckParams('foo', 'text', options); + it('returns true if the input type is text and the term is undefined', () => { + const options = {}; + const params = autocompleteCheckParams('foo', 'text', options); assert.isTrue(evaluate.apply(checkContext, params)); }); - it('returns true if the input type is tel and the term is off', function () { - var options = {}; - var params = autocompleteCheckParams('off', 'tel', options); + it('returns true if the input type is tel and the term is off', () => { + const options = {}; + const params = autocompleteCheckParams('off', 'tel', options); assert.isTrue(evaluate.apply(checkContext, params)); }); - it('returns true if the input type is url and the term is on', function () { - var options = {}; - var params = autocompleteCheckParams('on', 'url', options); + it('returns true if the input type is url and the term is on', () => { + const options = {}; + const params = autocompleteCheckParams('on', 'url', options); assert.isTrue(evaluate.apply(checkContext, params)); }); - it('returns true if the input type is foobar and the term is undefined', function () { - var options = {}; - var params = autocompleteCheckParams('foo', 'foobar', options); + it('returns true if the input type is foobar and the term is undefined', () => { + const options = {}; + const params = autocompleteCheckParams('foo', 'foobar', options); assert.isTrue(evaluate.apply(checkContext, params)); }); - it('returns true if the input type is email and the term is username', function () { - var options = {}; - var params = autocompleteCheckParams('username', 'email', options); + it('returns true if the input type is email and the term is username', () => { + const options = {}; + const params = autocompleteCheckParams('username', 'email', options); assert.isTrue(evaluate.apply(checkContext, params)); }); - it('returns false if the input type is text and the term maps to an empty array', function () { - var options = { foo: [] }; - var params = autocompleteCheckParams('foo', 'text', options); + it('returns false if the input type is text and the term maps to an empty array', () => { + const options = { foo: [] }; + const params = autocompleteCheckParams('foo', 'text', options); assert.isFalse(evaluate.apply(checkContext, params)); }); - it('returns false if the input type is month and term is bday-month', function () { - var options = {}; - var params = autocompleteCheckParams('bday-month', 'month', options); + it('returns false if the input type is month and term is bday-month', () => { + const options = {}; + const params = autocompleteCheckParams('bday-month', 'month', options); assert.isFalse(evaluate.apply(checkContext, params)); }); - it('returns false if the input type is MONTH (case-insensitive & sanitized) and term is bday-month', function () { - var options = {}; - var params = autocompleteCheckParams('bday-month', ' MONTH ', options); + it('returns false if the input type is MONTH (case-insensitive & sanitized) and term is bday-month', () => { + const options = {}; + const params = autocompleteCheckParams( + 'bday-month', + ' MONTH ', + options + ); assert.isFalse(evaluate.apply(checkContext, params)); }); }); diff --git a/test/checks/forms/autocomplete-valid.js b/test/checks/forms/autocomplete-valid.js index 1df38fe56..b8483ebd8 100644 --- a/test/checks/forms/autocomplete-valid.js +++ b/test/checks/forms/autocomplete-valid.js @@ -1,34 +1,32 @@ -describe('autocomplete-valid', function () { - 'use strict'; +describe('autocomplete-valid', () => { + const fixture = document.getElementById('fixture'); + const checkSetup = axe.testUtils.checkSetup; + const checkContext = axe.testUtils.MockCheckContext(); + const evaluate = axe.testUtils.getCheckEvaluate('autocomplete-valid'); - var fixture = document.getElementById('fixture'); - var checkSetup = axe.testUtils.checkSetup; - var checkContext = axe.testUtils.MockCheckContext(); - var evaluate = axe.testUtils.getCheckEvaluate('autocomplete-valid'); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; checkContext.reset(); }); - it('returns true if autocomplete is valid', function () { - var params = checkSetup(''); + it('returns true if autocomplete is valid', () => { + const params = checkSetup(''); assert.isTrue(evaluate.apply(checkContext, params)); }); - it('returns false if autocomplete is not valid', function () { - var params = checkSetup(''); + it('returns false if autocomplete is not valid', () => { + const params = checkSetup(''); assert.isFalse(evaluate.apply(checkContext, params)); }); - it('returns undefined (incomplete) if autocomplete is ignored', function () { - var params = checkSetup(''); + it('returns undefined (incomplete) if autocomplete is ignored', () => { + const params = checkSetup(''); assert.isUndefined(evaluate.apply(checkContext, params)); }); - it('uses options to change what is valid autocomplete', function () { - var options = { stateTerms: ['foo'] }; - var params = checkSetup( + it('uses options to change what is valid autocomplete', () => { + const options = { stateTerms: ['foo'] }; + const params = checkSetup( '', options ); diff --git a/test/checks/keyboard/accesskeys.js b/test/checks/keyboard/accesskeys.js index 5b99213f8..9a0e5a1ef 100644 --- a/test/checks/keyboard/accesskeys.js +++ b/test/checks/keyboard/accesskeys.js @@ -1,6 +1,4 @@ describe('accesskeys', () => { - 'use strict'; - const checkSetup = axe.testUtils.checkSetup; const checkContext = axe.testUtils.MockCheckContext(); const checkEvaluate = axe.testUtils.getCheckEvaluate('accesskeys'); diff --git a/test/checks/keyboard/focusable-content.js b/test/checks/keyboard/focusable-content.js index 3650cf831..8a13a02a4 100644 --- a/test/checks/keyboard/focusable-content.js +++ b/test/checks/keyboard/focusable-content.js @@ -1,125 +1,131 @@ -describe('focusable-content tests', function () { - 'use strict'; +describe('focusable-content tests', () => { + const html = axe.testUtils.html; - var check; - var fixture = document.getElementById('fixture'); - var fixtureSetup = axe.testUtils.fixtureSetup; - var shadowSupported = axe.testUtils.shadowSupport.v1; - var checkContext = axe.testUtils.MockCheckContext(); - var checkSetup = axe.testUtils.checkSetup; + let check; + const fixture = document.getElementById('fixture'); + const fixtureSetup = axe.testUtils.fixtureSetup; + const shadowSupported = axe.testUtils.shadowSupport.v1; + const checkContext = axe.testUtils.MockCheckContext(); + const checkSetup = axe.testUtils.checkSetup; - before(function () { + before(() => { check = checks['focusable-content']; }); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; axe._tree = undefined; checkContext.reset(); }); - it('returns false when there are no focusable content elements (content element `div` is not focusable)', function () { - var params = checkSetup( - '
' + '
Content
' + '
' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns false when there are no focusable content elements (content element `div` is not focusable)', () => { + const params = checkSetup(html` +
+
Content
+
+ `); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); }); - it('returns false when content element is taken out of focusable order (tabindex = -1)', function () { - var params = checkSetup( - '
' + '' + '
' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns false when content element is taken out of focusable order (tabindex = -1)', () => { + const params = checkSetup(html` +
+ +
+ `); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); }); - it('returns false when element is focusable (only checks if contents are focusable)', function () { - var params = checkSetup( - '
' + - '

' + - '
' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns false when element is focusable (only checks if contents are focusable)', () => { + const params = checkSetup(html` +
+

+
+ `); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); }); - it('returns false when all content elements are not focusable', function () { - var params = checkSetup( - '
' + - '' + - '' + - '' + - '
' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns false when all content elements are not focusable', () => { + const params = checkSetup(html` +
+ + + +
+ `); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); }); - it('returns true when one deeply nested content element is focusable', function () { - var params = checkSetup( - '
' + - '
' + - '
' + - '' + - '
' + - '
' + - '
' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns true when one deeply nested content element is focusable', () => { + const params = checkSetup(html` +
+
+
+ +
+
+
+ `); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns true when content element can be focused', function () { - var params = checkSetup( - '
' + '' + '
' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns true when content element can be focused', () => { + const params = checkSetup(html` +
+ +
+ `); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns true when any one of the many content elements can be focused', function () { - var params = checkSetup( - '
' + - '' + - '' + - '' + - '

' + - '
' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns true when any one of the many content elements can be focused', () => { + const params = checkSetup(html` +
+ + + +

+
+ `); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - describe('shadowDOM - focusable content', function () { + describe('shadowDOM - focusable content', () => { before(function () { if (!shadowSupported) { this.skip(); } }); - it('returns true when content element can be focused', function () { - fixtureSetup('
' + '
'); - var node = fixture.querySelector('#target'); - var shadow = node.attachShadow({ mode: 'open' }); + it('returns true when content element can be focused', () => { + fixtureSetup(html`
`); + const node = fixture.querySelector('#target'); + const shadow = node.attachShadow({ mode: 'open' }); shadow.innerHTML = ''; axe._tree = axe.utils.getFlattenedTree(fixture); axe._selectorData = axe.utils.getSelectorData(axe._tree); - var virtualNode = axe.utils.getNodeFromTree(axe._tree[0], node); - var actual = check.evaluate.call(checkContext, node, {}, virtualNode); + const virtualNode = axe.utils.getNodeFromTree(axe._tree[0], node); + const actual = check.evaluate.call(checkContext, node, {}, virtualNode); assert.isTrue(actual); }); - it('returns false when no focusable content', function () { - fixtureSetup('
' + '
'); - var node = fixture.querySelector('#target'); - var shadow = node.attachShadow({ mode: 'open' }); + it('returns false when no focusable content', () => { + fixtureSetup(html`
`); + const node = fixture.querySelector('#target'); + const shadow = node.attachShadow({ mode: 'open' }); shadow.innerHTML = '

just some text

'; axe._tree = axe.utils.getFlattenedTree(fixture); axe._selectorData = axe.utils.getSelectorData(axe._tree); - var virtualNode = axe.utils.getNodeFromTree(axe._tree[0], node); - var actual = check.evaluate.call(checkContext, node, {}, virtualNode); + const virtualNode = axe.utils.getNodeFromTree(axe._tree[0], node); + const actual = check.evaluate.call(checkContext, node, {}, virtualNode); assert.isFalse(actual); }); }); diff --git a/test/checks/keyboard/focusable-disabled.js b/test/checks/keyboard/focusable-disabled.js index ffe1536f9..f00962863 100644 --- a/test/checks/keyboard/focusable-disabled.js +++ b/test/checks/keyboard/focusable-disabled.js @@ -1,105 +1,103 @@ -describe('focusable-disabled', function () { - 'use strict'; +describe('focusable-disabled', () => { + const html = axe.testUtils.html; - var check; - var fixture = document.getElementById('fixture'); - var fixtureSetup = axe.testUtils.fixtureSetup; - var shadowSupported = axe.testUtils.shadowSupport.v1; - var checkContext = axe.testUtils.MockCheckContext(); - var checkSetup = axe.testUtils.checkSetup; + let check; + const fixture = document.getElementById('fixture'); + const fixtureSetup = axe.testUtils.fixtureSetup; + const checkContext = axe.testUtils.MockCheckContext(); + const checkSetup = axe.testUtils.checkSetup; - before(function () { + before(() => { check = checks['focusable-disabled']; }); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; axe._tree = undefined; axe._selectorData = undefined; checkContext.reset(); }); - it('returns true when content not focusable by default (no tabbable elements)', function () { - var params = checkSetup(''); - var actual = check.evaluate.apply(checkContext, params); + it('returns true when content not focusable by default (no tabbable elements)', () => { + const params = checkSetup( + '' + ); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns true when content hidden through CSS (no tabbable elements)', function () { - var params = checkSetup( + it('returns true when content hidden through CSS (no tabbable elements)', () => { + const params = checkSetup( '' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns true when content made unfocusable through disabled (no tabbable elements)', function () { - var params = checkSetup( + it('returns true when content made unfocusable through disabled (no tabbable elements)', () => { + const params = checkSetup( '' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns true when content made unfocusable through disabled fieldset', function () { - var params = checkSetup( + it('returns true when content made unfocusable through disabled fieldset', () => { + const params = checkSetup( '' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - (shadowSupported ? it : xit)( - 'returns false when content is in a disabled fieldset but in another shadow tree', - function () { - var fieldset = document.createElement('fieldset'); - fieldset.setAttribute('disabled', 'true'); - fieldset.setAttribute('aria-hidden', 'true'); - fieldset.setAttribute('id', 'target'); - var disabledInput = document.createElement('input'); - fieldset.appendChild(disabledInput); - var shadowRoot = document.createElement('div'); - fieldset.appendChild(shadowRoot); - var shadow = shadowRoot.attachShadow({ mode: 'open' }); - shadow.innerHTML = ''; - var params = checkSetup(fieldset); - - var actual = check.evaluate.apply(checkContext, params); - - assert.isFalse(actual); - } - ); - - it('returns false when content is in the legend of a disabled fieldset', function () { - var params = checkSetup( + it('returns false when content is in a disabled fieldset but in another shadow tree', () => { + const fieldset = document.createElement('fieldset'); + fieldset.setAttribute('disabled', 'true'); + fieldset.setAttribute('aria-hidden', 'true'); + fieldset.setAttribute('id', 'target'); + const disabledInput = document.createElement('input'); + fieldset.appendChild(disabledInput); + const shadowRoot = document.createElement('div'); + fieldset.appendChild(shadowRoot); + const shadow = shadowRoot.attachShadow({ mode: 'open' }); + shadow.innerHTML = ''; + const params = checkSetup(fieldset); + + const actual = check.evaluate.apply(checkContext, params); + + assert.isFalse(actual); + }); + + it('returns false when content is in the legend of a disabled fieldset', () => { + const params = checkSetup( '' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); }); - it('returns false when content is in an aria-hidden but not disabled fieldset', function () { - var params = checkSetup( + it('returns false when content is in an aria-hidden but not disabled fieldset', () => { + const params = checkSetup( '' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); }); - it('returns true when focusable off screen link (cannot be disabled)', function () { - var params = checkSetup( + it('returns true when focusable off screen link (cannot be disabled)', () => { + const params = checkSetup( '' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); assert.lengthOf(checkContext._relatedNodes, 0); }); - it('returns false when focusable form field only disabled through ARIA', function () { - var params = checkSetup( + it('returns false when focusable form field only disabled through ARIA', () => { + const params = checkSetup( '' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); assert.lengthOf(checkContext._relatedNodes, 1); assert.deepEqual( @@ -108,18 +106,19 @@ describe('focusable-disabled', function () { ); }); - it('returns false when focusable SELECT element that can be disabled', function () { - var params = checkSetup( - '' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns false when focusable SELECT element that can be disabled', () => { + const params = checkSetup(html` + + `); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); assert.lengthOf(checkContext._relatedNodes, 1); assert.deepEqual( @@ -128,92 +127,94 @@ describe('focusable-disabled', function () { ); }); - it('returns true when focusable AREA element (cannot be disabled)', function () { - var params = checkSetup( - '
' + - '' + - 'Mozilla' + - '' + - '
' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns true when focusable AREA element (cannot be disabled)', () => { + const params = checkSetup(html` +
+ + Mozilla + +
+ `); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - (shadowSupported ? it : xit)( - 'returns false when focusable content inside shadowDOM, that can be disabled', - function () { - // Note: - // `testUtils.checkSetup` does not work for shadowDOM - // as `axe._tree` and `axe._selectorData` needs to be updated after shadowDOM construction - fixtureSetup('
'); - var node = fixture.querySelector('#target'); - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = ''; - axe.testUtils.flatTreeSetup(fixture); - axe._selectorData = axe.utils.getSelectorData(axe._tree); - var virtualNode = axe.utils.getNodeFromTree(node); - var actual = check.evaluate.call(checkContext, node, {}, virtualNode); - assert.isFalse(actual); - } - ); - - it('returns true when focusable target that cannot be disabled', function () { - var params = checkSetup( + it('returns false when focusable content inside shadowDOM, that can be disabled', () => { + // Note: + // `testUtils.checkSetup` does not work for shadowDOM + // as `axe._tree` and `axe._selectorData` needs to be updated after shadowDOM construction + fixtureSetup('
'); + const node = fixture.querySelector('#target'); + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = ''; + axe.testUtils.flatTreeSetup(fixture); + axe._selectorData = axe.utils.getSelectorData(axe._tree); + const virtualNode = axe.utils.getNodeFromTree(node); + const actual = check.evaluate.call(checkContext, node, {}, virtualNode); + assert.isFalse(actual); + }); + + it('returns true when focusable target that cannot be disabled', () => { + const params = checkSetup( '' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns false when focusable target that can be disabled', function () { - var params = checkSetup( + it('returns false when focusable target that can be disabled', () => { + const params = checkSetup( '' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); }); - it('returns true if there is a focusable element and modal is open', function () { - var params = checkSetup( - '' + - '
Modal
' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns true if there is a focusable element and modal is open', () => { + const params = checkSetup(html` + +
Modal
+ `); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns undefined when the control has onfocus', function () { - var params = checkSetup( + it('returns undefined when the control has onfocus', () => { + const params = checkSetup( '' ); assert.isUndefined(check.evaluate.apply(checkContext, params)); }); - it('returns undefined when all focusable controls have onfocus events', function () { - var params = checkSetup( - '' - ); + it('returns undefined when all focusable controls have onfocus events', () => { + const params = checkSetup(html` + + `); assert.isUndefined(check.evaluate.apply(checkContext, params)); }); - it('returns false when some, but not all focusable controls have onfocus events', function () { - var params = checkSetup( - '' - ); + it('returns false when some, but not all focusable controls have onfocus events', () => { + const params = checkSetup(html` + + `); assert.isFalse(check.evaluate.apply(checkContext, params)); }); it('returns undefined when control has 0 width and height and pointer events: none (focus trap bumper)', () => { - var params = checkSetup( + const params = checkSetup( '' ); assert.isUndefined(check.evaluate.apply(checkContext, params)); diff --git a/test/checks/keyboard/focusable-element.js b/test/checks/keyboard/focusable-element.js index d0d1cd5a0..d6b5c019f 100644 --- a/test/checks/keyboard/focusable-element.js +++ b/test/checks/keyboard/focusable-element.js @@ -1,94 +1,92 @@ -describe('focusable-element tests', function () { - 'use strict'; +describe('focusable-element tests', () => { + let check; + const fixture = document.getElementById('fixture'); + const checkContext = axe.testUtils.MockCheckContext(); + const checkSetup = axe.testUtils.checkSetup; - var check; - var fixture = document.getElementById('fixture'); - var checkContext = axe.testUtils.MockCheckContext(); - var checkSetup = axe.testUtils.checkSetup; - - before(function () { + before(() => { check = checks['focusable-element']; }); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; axe._tree = undefined; checkContext.reset(); }); - it('returns true when element is focusable', function () { - var params = checkSetup(''); - var actual = check.evaluate.apply(checkContext, params); + it('returns true when element is focusable', () => { + const params = checkSetup(''); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns false when element made not focusable by tabindex', function () { - var params = checkSetup( + it('returns false when element made not focusable by tabindex', () => { + const params = checkSetup( '' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); }); - it('returns false when element is not focusable by default', function () { - var params = checkSetup('

I hold some text

'); - var actual = check.evaluate.apply(checkContext, params); + it('returns false when element is not focusable by default', () => { + const params = checkSetup('

I hold some text

'); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); }); - it('returns true when element made focusable by tabindex', function () { - var params = checkSetup( + it('returns true when element made focusable by tabindex', () => { + const params = checkSetup( '

I hold some text

' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns true when element made focusable by contenteditable', function () { - var params = checkSetup( + it('returns true when element made focusable by contenteditable', () => { + const params = checkSetup( '

I hold some text

' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns true when element made focusable by contenteditable="true"', function () { - var params = checkSetup( + it('returns true when element made focusable by contenteditable="true"', () => { + const params = checkSetup( '

I hold some text

' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns false when element made focusable by contenteditable="false"', function () { - var params = checkSetup( + it('returns false when element made focusable by contenteditable="false"', () => { + const params = checkSetup( '

I hold some text

' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); }); - it('returns true when element made focusable by contenteditable="invalid" and parent is contenteditable', function () { - var params = checkSetup( + it('returns true when element made focusable by contenteditable="invalid" and parent is contenteditable', () => { + const params = checkSetup( '

I hold some text

' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns false when element made focusable by contenteditable="invalid" and parent is not contenteditable', function () { - var params = checkSetup( + it('returns false when element made focusable by contenteditable="invalid" and parent is not contenteditable', () => { + const params = checkSetup( '

I hold some text

' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); }); - it('returns false when element made focusable by contenteditable="invalid" and parent is contenteditable="false"', function () { - var params = checkSetup( + it('returns false when element made focusable by contenteditable="invalid" and parent is contenteditable="false"', () => { + const params = checkSetup( '

I hold some text

' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); }); }); diff --git a/test/checks/keyboard/focusable-modal-open.js b/test/checks/keyboard/focusable-modal-open.js index bb7693573..fc5a10ad3 100644 --- a/test/checks/keyboard/focusable-modal-open.js +++ b/test/checks/keyboard/focusable-modal-open.js @@ -1,50 +1,50 @@ -describe('focusable-modal-open', function () { - 'use strict'; +describe('focusable-modal-open', () => { + const html = axe.testUtils.html; - var check; - var fixture = document.getElementById('fixture'); - var checkContext = axe.testUtils.MockCheckContext(); - var checkSetup = axe.testUtils.checkSetup; + let check; + const fixture = document.getElementById('fixture'); + const checkContext = axe.testUtils.MockCheckContext(); + const checkSetup = axe.testUtils.checkSetup; - before(function () { + before(() => { check = checks['focusable-modal-open']; }); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; axe._tree = undefined; axe._selectorData = undefined; checkContext.reset(); }); - it('returns true when no modal is open', function () { - var params = checkSetup( - '' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns true when no modal is open', () => { + const params = checkSetup(html` + + `); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns undefined if a modal is open', function () { - var params = checkSetup( - '' + - '
Modal
' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns undefined if a modal is open', () => { + const params = checkSetup(html` + +
Modal
+ `); + const actual = check.evaluate.apply(checkContext, params); assert.isUndefined(actual); }); - it('sets the tabbable elements as related nodes', function () { - var params = checkSetup( - '' + - '
Modal
' - ); + it('sets the tabbable elements as related nodes', () => { + const params = checkSetup(html` + +
Modal
+ `); check.evaluate.apply(checkContext, params); assert.lengthOf(checkContext._relatedNodes, 1); assert.deepEqual( diff --git a/test/checks/keyboard/focusable-no-name.js b/test/checks/keyboard/focusable-no-name.js index e170eb838..c9e749428 100644 --- a/test/checks/keyboard/focusable-no-name.js +++ b/test/checks/keyboard/focusable-no-name.js @@ -1,21 +1,18 @@ -describe('focusable-no-name', function () { - 'use strict'; +describe('focusable-no-name', () => { + const fixture = document.getElementById('fixture'); + const checkSetup = axe.testUtils.checkSetup; + const shadowCheckSetup = axe.testUtils.shadowCheckSetup; - var fixture = document.getElementById('fixture'); - var checkSetup = axe.testUtils.checkSetup; - var shadowCheckSetup = axe.testUtils.shadowCheckSetup; - var shadowSupport = axe.testUtils.shadowSupport; + const checkContext = axe.testUtils.MockCheckContext(); - var checkContext = axe.testUtils.MockCheckContext(); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; axe._tree = undefined; checkContext.reset(); }); - it('should pass if tabindex < 0', function () { - var params = checkSetup(''); + it('should pass if tabindex < 0', () => { + const params = checkSetup(''); assert.isFalse( axe.testUtils .getCheckEvaluate('focusable-no-name') @@ -23,8 +20,8 @@ describe('focusable-no-name', function () { ); }); - it('should pass element is not natively focusable', function () { - var params = checkSetup(''); + it('should pass element is not natively focusable', () => { + const params = checkSetup(''); assert.isFalse( axe.testUtils .getCheckEvaluate('focusable-no-name') @@ -32,8 +29,8 @@ describe('focusable-no-name', function () { ); }); - it('should fail if element is tabbable with no name - native', function () { - var params = checkSetup(''); + it('should fail if element is tabbable with no name - native', () => { + const params = checkSetup(''); assert.isTrue( axe.testUtils .getCheckEvaluate('focusable-no-name') @@ -41,8 +38,8 @@ describe('focusable-no-name', function () { ); }); - it('should fail if element is tabbable with no name - ARIA', function () { - var params = checkSetup( + it('should fail if element is tabbable with no name - ARIA', () => { + const params = checkSetup( '' ); assert.isTrue( @@ -52,8 +49,8 @@ describe('focusable-no-name', function () { ); }); - it('should pass if the element is tabbable but has an accessible name', function () { - var params = checkSetup(''); + it('should pass if the element is tabbable but has an accessible name', () => { + const params = checkSetup(''); assert.isFalse( axe.testUtils .getCheckEvaluate('focusable-no-name') @@ -61,25 +58,22 @@ describe('focusable-no-name', function () { ); }); - (shadowSupport.v1 ? it : xit)( - 'should pass if the content is passed in with shadow DOM', - function () { - var params = shadowCheckSetup( - '
Content!
', - '' - ); + it('should pass if the content is passed in with shadow DOM', () => { + const params = shadowCheckSetup( + '
Content!
', + '' + ); - assert.isFalse( - axe.testUtils - .getCheckEvaluate('focusable-no-name') - .apply(checkContext, params) - ); - } - ); + assert.isFalse( + axe.testUtils + .getCheckEvaluate('focusable-no-name') + .apply(checkContext, params) + ); + }); - describe('Serial Virtual Node', function () { - it('should pass if tabindex < 0', function () { - var serialNode = new axe.SerialVirtualNode({ + describe('Serial Virtual Node', () => { + it('should pass if tabindex < 0', () => { + const serialNode = new axe.SerialVirtualNode({ nodeName: 'a', attributes: { tabindex: '-1', @@ -96,8 +90,8 @@ describe('focusable-no-name', function () { ); }); - it('should pass element is not natively focusable', function () { - var serialNode = new axe.SerialVirtualNode({ + it('should pass element is not natively focusable', () => { + const serialNode = new axe.SerialVirtualNode({ nodeName: 'span', attributes: { role: 'link', @@ -114,8 +108,8 @@ describe('focusable-no-name', function () { ); }); - it('should fail if element is tabbable with no name - native', function () { - var serialNode = new axe.SerialVirtualNode({ + it('should fail if element is tabbable with no name - native', () => { + const serialNode = new axe.SerialVirtualNode({ nodeName: 'a', attributes: { href: '#' @@ -132,8 +126,8 @@ describe('focusable-no-name', function () { ); }); - it('should return undefined if element is tabbable with no name nor children - native', function () { - var serialNode = new axe.SerialVirtualNode({ + it('should return undefined if element is tabbable with no name nor children - native', () => { + const serialNode = new axe.SerialVirtualNode({ nodeName: 'a', attributes: { href: '#' @@ -149,8 +143,8 @@ describe('focusable-no-name', function () { ); }); - it('should pass if the element is tabbable but has an accessible name', function () { - var serialNode = new axe.SerialVirtualNode({ + it('should pass if the element is tabbable but has an accessible name', () => { + const serialNode = new axe.SerialVirtualNode({ nodeName: 'a', attributes: { href: '#', diff --git a/test/checks/keyboard/focusable-not-tabbable.js b/test/checks/keyboard/focusable-not-tabbable.js index 60bd891d1..5b4c74d16 100644 --- a/test/checks/keyboard/focusable-not-tabbable.js +++ b/test/checks/keyboard/focusable-not-tabbable.js @@ -1,41 +1,40 @@ -describe('focusable-not-tabbable', function () { - 'use strict'; +describe('focusable-not-tabbable', () => { + const html = axe.testUtils.html; - var check; - var fixture = document.getElementById('fixture'); - var fixtureSetup = axe.testUtils.fixtureSetup; - var shadowSupported = axe.testUtils.shadowSupport.v1; - var checkContext = axe.testUtils.MockCheckContext(); - var checkSetup = axe.testUtils.checkSetup; + let check; + const fixture = document.getElementById('fixture'); + const fixtureSetup = axe.testUtils.fixtureSetup; + const checkContext = axe.testUtils.MockCheckContext(); + const checkSetup = axe.testUtils.checkSetup; - before(function () { + before(() => { check = checks['focusable-not-tabbable']; }); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; axe._tree = undefined; axe._selectorData = undefined; checkContext.reset(); }); - it('returns true when BUTTON removed from tab order through tabindex', function () { - var params = checkSetup( - '' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns true when BUTTON removed from tab order through tabindex', () => { + const params = checkSetup(html` + + `); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns false when LINK is in tab order', function () { - var params = checkSetup( - '' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns false when LINK is in tab order', () => { + const params = checkSetup(html` + + `); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); assert.lengthOf(checkContext._relatedNodes, 1); assert.deepEqual( @@ -44,11 +43,11 @@ describe('focusable-not-tabbable', function () { ); }); - it('returns true when focusable SUMMARY element, that cannot be disabled', function () { - var params = checkSetup( + it('returns true when focusable SUMMARY element, that cannot be disabled', () => { + const params = checkSetup( '' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); assert.lengthOf(checkContext._relatedNodes, 1); assert.deepEqual( @@ -57,123 +56,126 @@ describe('focusable-not-tabbable', function () { ); }); - it('returns true when TEXTAREA is in tab order, but 0 related nodes as TEXTAREA can be disabled', function () { - var params = checkSetup( - '' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns true when TEXTAREA is in tab order, but 0 related nodes as TEXTAREA can be disabled', () => { + const params = checkSetup(html` + + `); + const actual = check.evaluate.apply(checkContext, params); assert.lengthOf(checkContext._relatedNodes, 0); assert.isTrue(actual); }); - it('returns false when focusable AREA element', function () { - var params = checkSetup( - '
' + - '' + - 'Mozilla' + - '' + - '
' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns false when focusable AREA element', () => { + const params = checkSetup(html` +
+ + Mozilla + +
+ `); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); }); - it('returns true when aria-hidden=false does not negate aria-hidden true', function () { + it('returns true when aria-hidden=false does not negate aria-hidden true', () => { // Note: aria-hidden can't be reset once you've set it to true on an ancestor - var params = checkSetup( + const params = checkSetup( '' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - (shadowSupported ? it : xit)( - 'returns false when focusable text in shadowDOM', - function () { - // Note: - // `testUtils.checkSetup` does not work for shadowDOM - // as `axe._tree` and `axe._selectorData` needs to be updated after shadowDOM construction - fixtureSetup('`'); - var node = fixture.querySelector('#target'); - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = '

btn

'; - axe.testUtils.flatTreeSetup(fixture); - axe._selectorData = axe.utils.getSelectorData(axe._tree); - var virtualNode = axe.utils.getNodeFromTree(node); - var actual = check.evaluate.call(checkContext, node, {}, virtualNode); - assert.isFalse(actual); - } - ); - - it('returns false when focusable content through tabindex', function () { - var params = checkSetup( + it('returns false when focusable text in shadowDOM', () => { + // Note: + // `testUtils.checkSetup` does not work for shadowDOM + // as `axe._tree` and `axe._selectorData` needs to be updated after shadowDOM construction + fixtureSetup('`'); + const node = fixture.querySelector('#target'); + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = '

btn

'; + axe.testUtils.flatTreeSetup(fixture); + axe._selectorData = axe.utils.getSelectorData(axe._tree); + const virtualNode = axe.utils.getNodeFromTree(node); + const actual = check.evaluate.call(checkContext, node, {}, virtualNode); + assert.isFalse(actual); + }); + + it('returns false when focusable content through tabindex', () => { + const params = checkSetup( '' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); }); - it('returns false when focusable target that cannot be disabled', function () { - var params = checkSetup( + it('returns false when focusable target that cannot be disabled', () => { + const params = checkSetup( '' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isFalse(actual); }); - it('returns true when focusable target that can be disabled', function () { - var params = checkSetup( + it('returns true when focusable target that can be disabled', () => { + const params = checkSetup( '' ); - var actual = check.evaluate.apply(checkContext, params); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns true if there is a focusable element and modal is open', function () { - var params = checkSetup( - '' + - '
Modal
' - ); - var actual = check.evaluate.apply(checkContext, params); + it('returns true if there is a focusable element and modal is open', () => { + const params = checkSetup(html` + +
Modal
+ `); + const actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns undefined when the control has onfocus', function () { - var params = checkSetup( + it('returns undefined when the control has onfocus', () => { + const params = checkSetup( '' ); assert.isUndefined(check.evaluate.apply(checkContext, params)); }); - it('returns undefined when all focusable controls have onfocus events', function () { - var params = checkSetup( - '' - ); + it('returns undefined when all focusable controls have onfocus events', () => { + const params = checkSetup(html` + + `); assert.isUndefined(check.evaluate.apply(checkContext, params)); }); - it('returns false when some, but not all focusable controls have onfocus events', function () { - var params = checkSetup( - '' - ); + it('returns false when some, but not all focusable controls have onfocus events', () => { + const params = checkSetup(html` + + `); assert.isFalse(check.evaluate.apply(checkContext, params)); }); it('returns undefined when control has 0 width and height and pointer events: none (focus trap bumper)', () => { - var params = checkSetup( + const params = checkSetup( '' ); assert.isUndefined(check.evaluate.apply(checkContext, params)); diff --git a/test/checks/keyboard/frame-focusable-content.js b/test/checks/keyboard/frame-focusable-content.js index 450645191..6526cef5f 100644 --- a/test/checks/keyboard/frame-focusable-content.js +++ b/test/checks/keyboard/frame-focusable-content.js @@ -1,52 +1,52 @@ -describe('frame-focusable-content tests', function () { - var fixture = document.querySelector('#fixture'); - var queryFixture = axe.testUtils.queryFixture; - var frameFocusableContent = axe.testUtils.getCheckEvaluate( +describe('frame-focusable-content tests', () => { + const fixture = document.querySelector('#fixture'); + const queryFixture = axe.testUtils.queryFixture; + const frameFocusableContent = axe.testUtils.getCheckEvaluate( 'frame-focusable-content' ); - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should return true if element has no focusable content', function () { - var vNode = queryFixture('
Hello
'); + it('should return true if element has no focusable content', () => { + const vNode = queryFixture('
Hello
'); assert.isTrue(frameFocusableContent(null, null, vNode)); }); - it('should return true if element is empty', function () { - var vNode = queryFixture('
'); + it('should return true if element is empty', () => { + const vNode = queryFixture('
'); assert.isTrue(frameFocusableContent(null, null, vNode)); }); - it('should return true if element only has text content', function () { - var vNode = queryFixture('
Hello
'); + it('should return true if element only has text content', () => { + const vNode = queryFixture('
Hello
'); assert.isTrue(frameFocusableContent(null, null, vNode)); }); - it('should return false if element has focusable content', function () { - var vNode = queryFixture( + it('should return false if element has focusable content', () => { + const vNode = queryFixture( '
Hello
' ); assert.isFalse(frameFocusableContent(null, null, vNode)); }); - it('should return false if element has natively focusable content', function () { - var vNode = queryFixture( + it('should return false if element has natively focusable content', () => { + const vNode = queryFixture( '' ); assert.isFalse(frameFocusableContent(null, null, vNode)); }); - it('should return true if element is natively focusable but has tabindex=-1', function () { - var vNode = queryFixture( + it('should return true if element is natively focusable but has tabindex=-1', () => { + const vNode = queryFixture( '
' ); assert.isTrue(frameFocusableContent(null, null, vNode)); }); - it('should return false if element is natively focusable but has tabindex=0', function () { - var vNode = queryFixture( + it('should return false if element is natively focusable but has tabindex=0', () => { + const vNode = queryFixture( '
' ); assert.isFalse(frameFocusableContent(null, null, vNode)); diff --git a/test/checks/keyboard/landmark-is-top-level.js b/test/checks/keyboard/landmark-is-top-level.js index f990a0481..69fab6138 100644 --- a/test/checks/keyboard/landmark-is-top-level.js +++ b/test/checks/keyboard/landmark-is-top-level.js @@ -1,5 +1,4 @@ describe('landmark-is-top-level', () => { - const shadowSupported = axe.testUtils.shadowSupport.v1; const checkSetup = axe.testUtils.checkSetup; const shadowCheckSetup = axe.testUtils.shadowCheckSetup; const check = checks['landmark-is-top-level']; @@ -73,16 +72,13 @@ describe('landmark-is-top-level', () => { assert.deepEqual(checkContext._data, { role: 'banner' }); }); - (shadowSupported ? it : xit)( - 'should test if the landmark in shadow DOM is top level', - () => { - const params = shadowCheckSetup( - '
', - '
Main content
' - ); - axe.utils.getFlattenedTree(document.documentElement); - assert.isTrue(check.evaluate.apply(checkContext, params)); - assert.deepEqual(checkContext._data, { role: 'main' }); - } - ); + it('should test if the landmark in shadow DOM is top level', () => { + const params = shadowCheckSetup( + '
', + '
Main content
' + ); + axe.utils.getFlattenedTree(document.documentElement); + assert.isTrue(check.evaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._data, { role: 'main' }); + }); }); diff --git a/test/checks/keyboard/no-focusable-content.js b/test/checks/keyboard/no-focusable-content.js index e30841b41..7bfd604cd 100644 --- a/test/checks/keyboard/no-focusable-content.js +++ b/test/checks/keyboard/no-focusable-content.js @@ -1,48 +1,51 @@ -describe('no-focusable-content tests', function () { - var queryFixture = axe.testUtils.queryFixture; - var noFocusableContent = axe.testUtils.getCheckEvaluate( +describe('no-focusable-content tests', () => { + const html = axe.testUtils.html; + const queryFixture = axe.testUtils.queryFixture; + const noFocusableContent = axe.testUtils.getCheckEvaluate( 'no-focusable-content' ); - var checkSetup = axe.testUtils.checkSetup; - var check = checks['no-focusable-content']; - var checkContext = new axe.testUtils.MockCheckContext(); + const checkSetup = axe.testUtils.checkSetup; + const check = checks['no-focusable-content']; + const checkContext = new axe.testUtils.MockCheckContext(); - afterEach(function () { + afterEach(() => { checkContext.reset(); }); - it('should return true if element has no focusable content', function () { - var vNode = queryFixture(''); + it('should return true if element has no focusable content', () => { + const vNode = queryFixture( + '' + ); assert.isTrue(noFocusableContent(null, null, vNode)); }); - it('should return true if element is empty', function () { - var vNode = queryFixture(''); + it('should return true if element is empty', () => { + const vNode = queryFixture(''); assert.isTrue(noFocusableContent(null, null, vNode)); }); - it('should return true if element only has text content', function () { - var vNode = queryFixture(''); + it('should return true if element only has text content', () => { + const vNode = queryFixture(''); assert.isTrue(noFocusableContent(null, null, vNode)); }); - it('should return true if element has content which is focusable (tabindex=0) and does not have a widget role', function () { - var params = checkSetup( + it('should return true if element has content which is focusable (tabindex=0) and does not have a widget role', () => { + const params = checkSetup( '' ); assert.isTrue(noFocusableContent.apply(checkContext, params)); }); - it('should return true if element has content which has negative tabindex and non-widget role', function () { - var vNode = queryFixture( + it('should return true if element has content which has negative tabindex and non-widget role', () => { + const vNode = queryFixture( '' ); assert.isTrue(noFocusableContent(null, null, vNode)); }); - it('should return false if element has content which has negative tabindex and an explicit widget role', function () { - var params = checkSetup( + it('should return false if element has content which has negative tabindex and an explicit widget role', () => { + const params = checkSetup( '' ); @@ -51,8 +54,8 @@ describe('no-focusable-content tests', function () { assert.deepEqual(checkContext._relatedNodes, [params[2].children[0]]); }); - it('should return false if element has content which is natively focusable and has a widget role', function () { - var params = checkSetup( + it('should return false if element has content which is natively focusable and has a widget role', () => { + const params = checkSetup( '' ); @@ -61,8 +64,8 @@ describe('no-focusable-content tests', function () { assert.deepEqual(checkContext._relatedNodes, [params[2].children[0]]); }); - it('should add each focusable child as related nodes', function () { - var params = checkSetup( + it('should add each focusable child as related nodes', () => { + const params = checkSetup( '' ); @@ -74,8 +77,8 @@ describe('no-focusable-content tests', function () { ]); }); - it('should return false if element has natively focusable widget role content with negative tabindex', function () { - var params = checkSetup( + it('should return false if element has natively focusable widget role content with negative tabindex', () => { + const params = checkSetup( '' ); assert.isFalse(check.evaluate.apply(checkContext, params)); @@ -83,44 +86,49 @@ describe('no-focusable-content tests', function () { assert.deepEqual(checkContext._relatedNodes, [params[2].children[0]]); }); - it('should return true if element has content which is natively focusable and has a widget role but is disabled', function () { - var vNode = queryFixture( + it('should return true if element has content which is natively focusable and has a widget role but is disabled', () => { + const vNode = queryFixture( '' ); assert.isTrue(noFocusableContent(null, null, vNode)); }); - it('should return false if "disabled" is specified on an element which doesn\'t allow it', function () { - var params = checkSetup( + it('should return false if "disabled" is specified on an element which doesn\'t allow it', () => { + const params = checkSetup( '' ); assert.isFalse(noFocusableContent.apply(checkContext, params)); }); - it('should return true on span with negative tabindex (focusable, does not have a widget role)', function () { - var vNode = queryFixture( - ' some text ' + - 'JavaScript is able to focus this ' + - '' - ); + it('should return true on span with negative tabindex (focusable, does not have a widget role)', () => { + const vNode = queryFixture(html` + + some text + JavaScript is able to focus this + + `); assert.isTrue(noFocusableContent(null, null, vNode)); }); - it('should return true on aria-hidden span with negative tabindex (focusable, does not have a widget role)', function () { - var vNode = queryFixture( - ' some text ' + - ' ' + - '' - ); + it('should return true on aria-hidden span with negative tabindex (focusable, does not have a widget role)', () => { + const vNode = queryFixture(html` + + some text + + + `); assert.isTrue(noFocusableContent(null, null, vNode)); }); - it('should return true on nested span with tabindex=0 (focusable, does not have a widget role)', function () { - var vNode = queryFixture( - ' some text ' + - 'anyone is able to focus this ' + - '' - ); + it('should return true on nested span with tabindex=0 (focusable, does not have a widget role)', () => { + const vNode = queryFixture(html` + + some text + anyone is able to focus this + + `); assert.isTrue(noFocusableContent(null, null, vNode)); }); }); diff --git a/test/checks/keyboard/page-has-elm.js b/test/checks/keyboard/page-has-elm.js index 20e629516..c91a01fbd 100644 --- a/test/checks/keyboard/page-has-elm.js +++ b/test/checks/keyboard/page-has-elm.js @@ -1,90 +1,97 @@ -describe('page-has-*', function () { - 'use strict'; +describe('page-has-*', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var checkContext = new axe.testUtils.MockCheckContext(); - var checkSetup = axe.testUtils.checkSetup; - var shadowSupported = axe.testUtils.shadowSupport.v1; - var shadowCheckSetup = axe.testUtils.shadowCheckSetup; + const fixture = document.getElementById('fixture'); + const checkContext = new axe.testUtils.MockCheckContext(); + const checkSetup = axe.testUtils.checkSetup; + const shadowCheckSetup = axe.testUtils.shadowCheckSetup; - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; checkContext.reset(); axe._tree = undefined; }); - describe('evaluate', function () { - var evaluate = axe.testUtils.getCheckEvaluate('page-has-main'); + describe('evaluate', () => { + const evaluate = axe.testUtils.getCheckEvaluate('page-has-main'); - it('throws if there is no selector', function () { - assert.throws(function () { - var params = checkSetup('
No role
', undefined); + it('throws if there is no selector', () => { + assert.throws(() => { + const params = checkSetup('
No role
', undefined); checks['page-has-main'].evaluate.apply(checkContext, params); }); - assert.throws(function () { - var params = checkSetup('
No role
', {}); + assert.throws(() => { + const params = checkSetup('
No role
', {}); checks['page-has-main'].evaluate.apply(checkContext, params); }); - assert.throws(function () { - var badOptions = { selector: [] }; - var params = checkSetup('
No role
', badOptions); + assert.throws(() => { + const badOptions = { selector: [] }; + const params = checkSetup('
No role
', badOptions); checks['page-has-main'].evaluate.apply(checkContext, params); }); }); - it('returns true if there are any matching elements', function () { - var options = { selector: 'b' }; - var params = checkSetup('
No role
', options); + it('returns true if there are any matching elements', () => { + const options = { selector: 'b' }; + const params = checkSetup( + '
No role
', + options + ); assert.isTrue(evaluate.apply(checkContext, params)); }); - it('returns false if there are no matching elements', function () { - var options = { selector: 'i' }; - var params = checkSetup('
No role
', options); + it('returns false if there are no matching elements', () => { + const options = { selector: 'i' }; + const params = checkSetup( + '
No role
', + options + ); assert.isFalse(evaluate.apply(checkContext, params)); }); - it('does not find hidden elements', function () { - var options = { selector: 'b' }; - var params = checkSetup( + it('does not find hidden elements', () => { + const options = { selector: 'b' }; + const params = checkSetup( '
No role
', options ); assert.isFalse(evaluate.apply(checkContext, params)); }); - it('does find screen-reader only elements', function () { - var options = { selector: 'b' }; - var params = checkSetup( - '' + - '
No role
', + it('does find screen-reader only elements', () => { + const options = { selector: 'b' }; + const params = checkSetup( + html` + +
No role
+ `, options ); assert.isTrue(evaluate.apply(checkContext, params)); }); }); - describe('after', function () { - var after = checks['page-has-main'].after; + describe('after', () => { + const after = checks['page-has-main'].after; - it('sets all results to true if any are true', function () { - var results = [ + it('sets all results to true if any are true', () => { + const results = [ { result: true }, { result: false }, { result: undefined } @@ -96,8 +103,8 @@ describe('page-has-*', function () { ]); }); - it('Leave the results as is if none of them were true', function () { - var results = [ + it('Leave the results as is if none of them were true', () => { + const results = [ { result: false }, { result: false }, { result: undefined } @@ -106,103 +113,103 @@ describe('page-has-*', function () { }); }); - describe('page-has-main', function () { - var check = checks['page-has-main']; + describe('page-has-main', () => { + const check = checks['page-has-main']; - it('should return false if no div has role property', function () { - var params = checkSetup('
No role
', check.options); - var mainIsFound = check.evaluate.apply(checkContext, params); + it('should return false if no div has role property', () => { + const params = checkSetup( + '
No role
', + check.options + ); + const mainIsFound = check.evaluate.apply(checkContext, params); assert.isFalse(mainIsFound); }); - it('should return false if div has role not equal to main', function () { - var params = checkSetup( + it('should return false if div has role not equal to main', () => { + const params = checkSetup( '
Wrong role
', check.options ); - var mainIsFound = check.evaluate.apply(checkContext, params); + const mainIsFound = check.evaluate.apply(checkContext, params); assert.isFalse(mainIsFound); }); - it('should return true if main landmark exists', function () { - var params = checkSetup( + it('should return true if main landmark exists', () => { + const params = checkSetup( '
main landmark
', check.options ); - var mainIsFound = check.evaluate.apply(checkContext, params); + const mainIsFound = check.evaluate.apply(checkContext, params); assert.isTrue(mainIsFound); }); - it('should return true if one div has role equal to main', function () { - var params = checkSetup( + it('should return true if one div has role equal to main', () => { + const params = checkSetup( '
Div with role main
', check.options ); - var mainIsFound = check.evaluate.apply(checkContext, params); + const mainIsFound = check.evaluate.apply(checkContext, params); assert.isTrue(mainIsFound); }); - (shadowSupported ? it : xit)( - 'should return true if main is inside of shadow dom', - function () { - var params = shadowCheckSetup( - '
', - '
main landmark
', - check.options - ); - var mainIsFound = check.evaluate.apply(checkContext, params); - assert.isTrue(mainIsFound); - } - ); + it('should return true if main is inside of shadow dom', () => { + const params = shadowCheckSetup( + '
', + '
main landmark
', + check.options + ); + const mainIsFound = check.evaluate.apply(checkContext, params); + assert.isTrue(mainIsFound); + }); }); - describe('page-has-heading-one', function () { - var check = checks['page-has-heading-one']; + describe('page-has-heading-one', () => { + const check = checks['page-has-heading-one']; - it('should return false if div has role not equal to heading', function () { - var params = checkSetup( + it('should return false if div has role not equal to heading', () => { + const params = checkSetup( '
Wrong role
', check.options ); - var h1IsFound = check.evaluate.apply(checkContext, params); + const h1IsFound = check.evaluate.apply(checkContext, params); assert.isFalse(h1IsFound); }); - it('should return false if div has role heading but not aria-level=1', function () { - var params = checkSetup( + it('should return false if div has role heading but not aria-level=1', () => { + const params = checkSetup( '
Wrong role
', check.options ); - var h1IsFound = check.evaluate.apply(checkContext, params); + const h1IsFound = check.evaluate.apply(checkContext, params); assert.isFalse(h1IsFound); }); - it('should return true if h1 exists', function () { - var params = checkSetup('

My heading

', check.options); - var h1IsFound = check.evaluate.apply(checkContext, params); + it('should return true if h1 exists', () => { + const params = checkSetup( + '

My heading

', + check.options + ); + const h1IsFound = check.evaluate.apply(checkContext, params); assert.isTrue(h1IsFound); }); - it('should return true if a div has role=heading and aria-level=1', function () { - var params = checkSetup( + it('should return true if a div has role=heading and aria-level=1', () => { + const params = checkSetup( '
Diversity heading
', check.options ); - var h1IsFound = check.evaluate.apply(checkContext, params); + const h1IsFound = check.evaluate.apply(checkContext, params); assert.isTrue(h1IsFound); }); - (shadowSupported ? it : xit)( - 'should return true if h1 is inside of shadow dom', - function () { - var params = shadowCheckSetup( - '
', - '

Shady Heading

', - check.options - ); - var h1IsFound = check.evaluate.apply(checkContext, params); - assert.isTrue(h1IsFound); - } - ); + it('should return true if h1 is inside of shadow dom', () => { + const params = shadowCheckSetup( + '
', + '

Shady Heading

', + check.options + ); + const h1IsFound = check.evaluate.apply(checkContext, params); + assert.isTrue(h1IsFound); + }); }); }); diff --git a/test/checks/keyboard/page-no-duplicate.js b/test/checks/keyboard/page-no-duplicate.js index a54677e7e..2df7a1433 100644 --- a/test/checks/keyboard/page-no-duplicate.js +++ b/test/checks/keyboard/page-no-duplicate.js @@ -1,8 +1,8 @@ describe('page-no-duplicate', () => { + const html = axe.testUtils.html; const fixture = document.getElementById('fixture'); const checkContext = new axe.testUtils.MockCheckContext(); const checkSetup = axe.testUtils.checkSetup; - const shadowSupported = axe.testUtils.shadowSupport.v1; const check = checks['page-no-duplicate-main']; @@ -65,52 +65,46 @@ describe('page-no-duplicate', () => { assert.isTrue(check.evaluate.apply(checkContext, params)); }); - (shadowSupported ? it : xit)( - 'should return false if there is a second matching element inside the shadow dom', - () => { - const options = { selector: 'main' }; - const div = document.createElement('div'); - div.innerHTML = '
'; - - const shadow = div - .querySelector('#shadow') - .attachShadow({ mode: 'open' }); - shadow.innerHTML = '
'; - axe.testUtils.fixtureSetup(div); - const vNode = axe.utils.querySelectorAll(axe._tree, '#target')[0]; - - assert.isFalse( - check.evaluate.call(checkContext, vNode.actualNode, options, vNode) - ); - assert.deepEqual(checkContext._relatedNodes, [ - shadow.querySelector('main') - ]); - } - ); - - (shadowSupported ? it : xit)( - 'should return true if there is a second matching element inside the shadow dom but only one is visible to screenreaders', - () => { - const options = { selector: 'main' }; - const div = document.createElement('div'); - div.innerHTML = - '
'; - - const shadow = div - .querySelector('#shadow') - .attachShadow({ mode: 'open' }); - shadow.innerHTML = '
'; - axe.testUtils.fixtureSetup(div); - const vNode = axe.utils.querySelectorAll(axe._tree, '#target')[0]; + it('should return false if there is a second matching element inside the shadow dom', () => { + const options = { selector: 'main' }; + const div = document.createElement('div'); + div.innerHTML = '
'; + + const shadow = div + .querySelector('#shadow') + .attachShadow({ mode: 'open' }); + shadow.innerHTML = '
'; + axe.testUtils.fixtureSetup(div); + const vNode = axe.utils.querySelectorAll(axe._tree, '#target')[0]; + + assert.isFalse( + check.evaluate.call(checkContext, vNode.actualNode, options, vNode) + ); + assert.deepEqual(checkContext._relatedNodes, [ + shadow.querySelector('main') + ]); + }); - assert.isTrue( - check.evaluate.call(checkContext, vNode.actualNode, options, vNode) - ); - assert.deepEqual(checkContext._relatedNodes, [ - shadow.querySelector('main') - ]); - } - ); + it('should return true if there is a second matching element inside the shadow dom but only one is visible to screenreaders', () => { + const options = { selector: 'main' }; + const div = document.createElement('div'); + div.innerHTML = + '
'; + + const shadow = div + .querySelector('#shadow') + .attachShadow({ mode: 'open' }); + shadow.innerHTML = '
'; + axe.testUtils.fixtureSetup(div); + const vNode = axe.utils.querySelectorAll(axe._tree, '#target')[0]; + + assert.isTrue( + check.evaluate.call(checkContext, vNode.actualNode, options, vNode) + ); + assert.deepEqual(checkContext._relatedNodes, [ + shadow.querySelector('main') + ]); + }); }); describe('option.nativeScopeFilter', () => { @@ -120,9 +114,12 @@ describe('page-no-duplicate', () => { nativeScopeFilter: 'main' }; const params = checkSetup( - '
' + - '
' + - '
', + html` +
+
+
+
+ `, options ); assert.isTrue(check.evaluate.apply(checkContext, params)); @@ -134,9 +131,12 @@ describe('page-no-duplicate', () => { nativeScopeFilter: 'main' }; const params = checkSetup( - '
' + - '
' + - '
', + html` +
+
+
+
+ `, options ); assert.isFalse(check.evaluate.apply(checkContext, params)); @@ -148,36 +148,35 @@ describe('page-no-duplicate', () => { nativeScopeFilter: 'article' }; const params = checkSetup( - '
' + - '
Article footer
' + - '
' + - '
Body footer
', + html` +
+
Article footer
+
+
Body footer
+ `, options ); assert.isTrue(check.evaluate.apply(checkContext, params)); }); - (shadowSupported ? it : xit)( - 'elements if its ancestor is outside the shadow DOM tree', - () => { - const options = { - selector: 'footer', - nativeScopeFilter: 'header' - }; + it('elements if its ancestor is outside the shadow DOM tree', () => { + const options = { + selector: 'footer', + nativeScopeFilter: 'header' + }; - const div = document.createElement('div'); - div.innerHTML = - '
'; - div.querySelector('#shadow').attachShadow({ mode: 'open' }).innerHTML = - '
'; - axe.testUtils.fixtureSetup(div); - const vNode = axe.utils.querySelectorAll(axe._tree, '#target')[0]; + const div = document.createElement('div'); + div.innerHTML = + '
'; + div.querySelector('#shadow').attachShadow({ mode: 'open' }).innerHTML = + '
'; + axe.testUtils.fixtureSetup(div); + const vNode = axe.utils.querySelectorAll(axe._tree, '#target')[0]; - assert.isTrue( - check.evaluate.call(checkContext, vNode.actualNode, options, vNode) - ); - } - ); + assert.isTrue( + check.evaluate.call(checkContext, vNode.actualNode, options, vNode) + ); + }); }); describe('options.role', () => { @@ -187,7 +186,7 @@ describe('page-no-duplicate', () => { role: 'contentinfo' }; const params = checkSetup( - `
+ html`
@@ -204,7 +203,7 @@ describe('page-no-duplicate', () => { role: 'contentinfo' }; const params = checkSetup( - `
+ html`
@@ -224,35 +223,32 @@ describe('page-no-duplicate', () => { role: 'contentinfo' }; const params = checkSetup( - `
-
Article footer
-
-
Body footer
`, + html`
+
Article footer
+
+
Body footer
`, options ); assert.isTrue(check.evaluate.apply(checkContext, params)); }); - (shadowSupported ? it : xit)( - "should pass if element's ancestor is outside the shadow DOM tree", - () => { - const options = { - selector: 'footer', - role: 'contentinfo' - }; + it("should pass if element's ancestor is outside the shadow DOM tree", () => { + const options = { + selector: 'footer', + role: 'contentinfo' + }; - const div = document.createElement('div'); - div.innerHTML = - '
'; - div.querySelector('#shadow').attachShadow({ mode: 'open' }).innerHTML = - '
'; - axe.testUtils.fixtureSetup(div); - const vNode = axe.utils.querySelectorAll(axe._tree, '#target')[0]; + const div = document.createElement('div'); + div.innerHTML = + '
'; + div.querySelector('#shadow').attachShadow({ mode: 'open' }).innerHTML = + '
'; + axe.testUtils.fixtureSetup(div); + const vNode = axe.utils.querySelectorAll(axe._tree, '#target')[0]; - assert.isTrue( - check.evaluate.call(checkContext, vNode.actualNode, options, vNode) - ); - } - ); + assert.isTrue( + check.evaluate.call(checkContext, vNode.actualNode, options, vNode) + ); + }); }); }); diff --git a/test/checks/keyboard/tabindex.js b/test/checks/keyboard/tabindex.js index 1a32f9c34..b578f5978 100644 --- a/test/checks/keyboard/tabindex.js +++ b/test/checks/keyboard/tabindex.js @@ -1,15 +1,13 @@ -describe('tabindex', function () { - 'use strict'; +describe('tabindex', () => { + const checkContext = axe.testUtils.MockCheckContext(); + const queryFixture = axe.testUtils.queryFixture; - var checkContext = axe.testUtils.MockCheckContext(); - var queryFixture = axe.testUtils.queryFixture; - - afterEach(function () { + afterEach(() => { checkContext.reset(); }); - it('should fail if the testutils.jstabindex is >= 0', function () { - var vNode = queryFixture('
'); + it('should fail if the testutils.jstabindex is >= 0', () => { + const vNode = queryFixture('
'); assert.isFalse( axe.testUtils .getCheckEvaluate('tabindex') @@ -17,8 +15,8 @@ describe('tabindex', function () { ); }); - it('should pass if the tabindex is <= 0', function () { - var vNode = queryFixture('
'); + it('should pass if the tabindex is <= 0', () => { + const vNode = queryFixture('
'); assert.isTrue( axe.testUtils .getCheckEvaluate('tabindex') @@ -26,11 +24,11 @@ describe('tabindex', function () { ); }); - it('should look at the attribute and not the property', function () { - var node = document.createElement('div'); + it('should look at the attribute and not the property', () => { + const node = document.createElement('div'); node.setAttribute('tabindex', '1'); node.tabindex = null; - var tree = axe.testUtils.flatTreeSetup(node); + const tree = axe.testUtils.flatTreeSetup(node); assert.isFalse( axe.testUtils .getCheckEvaluate('tabindex') @@ -38,8 +36,8 @@ describe('tabindex', function () { ); }); - it('should pass if tabindex is NaN', function () { - var vNode = queryFixture('
'); + it('should pass if tabindex is NaN', () => { + const vNode = queryFixture('
'); assert.isTrue( axe.testUtils .getCheckEvaluate('tabindex') diff --git a/test/checks/label/alt-space-value.js b/test/checks/label/alt-space-value.js index 603a811ad..3392329c6 100644 --- a/test/checks/label/alt-space-value.js +++ b/test/checks/label/alt-space-value.js @@ -1,31 +1,29 @@ -describe('alt-space-value', function () { - 'use strict'; +describe('alt-space-value', () => { + const checkSetup = axe.testUtils.checkSetup; + const checkContext = axe.testUtils.MockCheckContext(); + const check = checks['alt-space-value']; - var checkSetup = axe.testUtils.checkSetup; - var checkContext = axe.testUtils.MockCheckContext(); - var check = checks['alt-space-value']; - - afterEach(function () { + afterEach(() => { checkContext.reset(); }); - it('should return true if alt contains a space character', function () { - var params = checkSetup(' '); + it('should return true if alt contains a space character', () => { + const params = checkSetup(' '); assert.isTrue(check.evaluate.apply(checkContext, params)); }); - it('should return true if alt contains a non-breaking space character', function () { - var params = checkSetup(' '); + it('should return true if alt contains a non-breaking space character', () => { + const params = checkSetup(' '); assert.isTrue(check.evaluate.apply(checkContext, params)); }); - it('should return false if alt attribute is empty', function () { - var params = checkSetup(''); + it('should return false if alt attribute is empty', () => { + const params = checkSetup(''); assert.isFalse(check.evaluate.apply(checkContext, params)); }); - it('should return false if alt attribute has a proper text value', function () { - var params = checkSetup('text content'); + it('should return false if alt attribute has a proper text value', () => { + const params = checkSetup('text content'); assert.isFalse(check.evaluate.apply(checkContext, params)); }); }); diff --git a/test/checks/label/duplicate-img-label.js b/test/checks/label/duplicate-img-label.js index 1a1de7d12..b840a3fae 100644 --- a/test/checks/label/duplicate-img-label.js +++ b/test/checks/label/duplicate-img-label.js @@ -1,20 +1,17 @@ -describe('duplicate-img-label', function () { - 'use strict'; +describe('duplicate-img-label', () => { + const fixture = document.getElementById('fixture'); + const checkSetup = axe.testUtils.checkSetup; - var fixture = document.getElementById('fixture'); - var checkSetup = axe.testUtils.checkSetup; - var shadowSupport = axe.testUtils.shadowSupport; - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; axe._tree = undefined; }); - it('should return false if no text is present', function () { + it('should return false if no text is present', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); axe.testUtils.flatTreeSetup(fixture); - var result = axe.testUtils.getCheckEvaluate('duplicate-img-label')( + const result = axe.testUtils.getCheckEvaluate('duplicate-img-label')( node, undefined, axe.utils.getNodeFromTree(node) @@ -22,10 +19,10 @@ describe('duplicate-img-label', function () { assert.isFalse(result); }); - it('should return false if aria-label duplicates img alt', function () { + it('should return false if aria-label duplicates img alt', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); axe.testUtils.flatTreeSetup(fixture); assert.isFalse( axe.testUtils.getCheckEvaluate('duplicate-img-label')( @@ -36,10 +33,10 @@ describe('duplicate-img-label', function () { ); }); - it('should return false if img and text have different text', function () { + it('should return false if img and text have different text', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); axe.testUtils.flatTreeSetup(fixture); assert.isFalse( axe.testUtils.getCheckEvaluate('duplicate-img-label')( @@ -50,10 +47,10 @@ describe('duplicate-img-label', function () { ); }); - it('should return true if img and text have the same text', function () { + it('should return true if img and text have the same text', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); axe.testUtils.flatTreeSetup(fixture); assert.isTrue( axe.testUtils.getCheckEvaluate('duplicate-img-label')( @@ -64,10 +61,10 @@ describe('duplicate-img-label', function () { ); }); - it('should return true if img has ARIA label with the same text', function () { + it('should return true if img has ARIA label with the same text', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); axe.testUtils.flatTreeSetup(fixture); assert.isTrue( axe.testUtils.getCheckEvaluate('duplicate-img-label')( @@ -78,9 +75,9 @@ describe('duplicate-img-label', function () { ); }); - it('should return false if img and text are both blank', function () { + it('should return false if img and text are both blank', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); axe.testUtils.flatTreeSetup(fixture); assert.isFalse( axe.testUtils.getCheckEvaluate('duplicate-img-label')( @@ -91,10 +88,10 @@ describe('duplicate-img-label', function () { ); }); - it('should return false if img and text have superset/subset text', function () { + it('should return false if img and text have superset/subset text', () => { fixture.innerHTML = ''; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); axe.testUtils.flatTreeSetup(fixture); assert.isFalse( axe.testUtils.getCheckEvaluate('duplicate-img-label')( @@ -105,10 +102,10 @@ describe('duplicate-img-label', function () { ); }); - it('should return false if img does not have required parent', function () { + it('should return false if img does not have required parent', () => { fixture.innerHTML = '
Plain text and more

Plain text

'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); axe.testUtils.flatTreeSetup(fixture); assert.isFalse( axe.testUtils.getCheckEvaluate('duplicate-img-label')( @@ -119,10 +116,10 @@ describe('duplicate-img-label', function () { ); }); - it('should support options.parentSelector', function () { + it('should support options.parentSelector', () => { fixture.innerHTML = '
Plain text
'; - var node = fixture.querySelector('#target'); + const node = fixture.querySelector('#target'); axe.testUtils.flatTreeSetup(fixture); assert.isFalse( axe.testUtils.getCheckEvaluate('duplicate-img-label')( @@ -133,64 +130,55 @@ describe('duplicate-img-label', function () { ); }); - (shadowSupport.v1 ? it : xit)( - 'should return true if the img is part of a shadow tree', - function () { - var button = document.createElement('div'); - button.setAttribute('role', 'button'); - button.innerHTML = 'My button'; - var shadow = button.attachShadow({ mode: 'open' }); - shadow.innerHTML = 'My button'; - fixture.appendChild(button); - axe.testUtils.flatTreeSetup(fixture); - var node = shadow.querySelector('#target'); - assert.isTrue( - axe.testUtils.getCheckEvaluate('duplicate-img-label')( - node, - undefined, - axe.utils.getNodeFromTree(node) - ) - ); - } - ); + it('should return true if the img is part of a shadow tree', () => { + const button = document.createElement('div'); + button.setAttribute('role', 'button'); + button.innerHTML = 'My button'; + const shadow = button.attachShadow({ mode: 'open' }); + shadow.innerHTML = 'My button'; + fixture.appendChild(button); + axe.testUtils.flatTreeSetup(fixture); + const node = shadow.querySelector('#target'); + assert.isTrue( + axe.testUtils.getCheckEvaluate('duplicate-img-label')( + node, + undefined, + axe.utils.getNodeFromTree(node) + ) + ); + }); - (shadowSupport.v1 ? it : xit)( - 'should return true if the img is a slotted element', - function () { - var button = document.createElement('div'); - button.setAttribute('role', 'button'); - button.innerHTML = 'My button'; - var shadow = button.attachShadow({ mode: 'open' }); - shadow.innerHTML = 'My button '; + it('should return true if the img is a slotted element', () => { + const button = document.createElement('div'); + button.setAttribute('role', 'button'); + button.innerHTML = 'My button'; + const shadow = button.attachShadow({ mode: 'open' }); + shadow.innerHTML = 'My button '; - fixture.appendChild(button); - axe.testUtils.flatTreeSetup(fixture); - var node = button.querySelector('#target'); - assert.isTrue( - axe.testUtils.getCheckEvaluate('duplicate-img-label')( - node, - undefined, - axe.utils.getNodeFromTree(node) - ) - ); - } - ); + fixture.appendChild(button); + axe.testUtils.flatTreeSetup(fixture); + const node = button.querySelector('#target'); + assert.isTrue( + axe.testUtils.getCheckEvaluate('duplicate-img-label')( + node, + undefined, + axe.utils.getNodeFromTree(node) + ) + ); + }); - (shadowSupport.v1 ? it : xit)( - 'should return false if the shadow img has a different text', - function () { - var button = document.createElement('div'); - button.setAttribute('role', 'button'); - button.innerHTML = 'My button'; - var shadow = button.attachShadow({ mode: 'open' }); - shadow.innerHTML = 'My image'; - var checkArgs = checkSetup(button); + it('should return false if the shadow img has a different text', () => { + const button = document.createElement('div'); + button.setAttribute('role', 'button'); + button.innerHTML = 'My button'; + const shadow = button.attachShadow({ mode: 'open' }); + shadow.innerHTML = 'My image'; + const checkArgs = checkSetup(button); - assert.isFalse( - axe.testUtils - .getCheckEvaluate('duplicate-img-label') - .apply(null, checkArgs) - ); - } - ); + assert.isFalse( + axe.testUtils + .getCheckEvaluate('duplicate-img-label') + .apply(null, checkArgs) + ); + }); }); diff --git a/test/checks/label/explicit.js b/test/checks/label/explicit.js index eafb00119..3bb6c0703 100644 --- a/test/checks/label/explicit.js +++ b/test/checks/label/explicit.js @@ -1,4 +1,5 @@ describe('explicit-label', () => { + const html = axe.testUtils.html; const fixtureSetup = axe.testUtils.fixtureSetup; const checkSetup = axe.testUtils.checkSetup; const checkEvaluate = axe.testUtils.getCheckEvaluate('explicit-label'); @@ -23,11 +24,11 @@ describe('explicit-label', () => { }); it('returns false if an empty label is present that uses aria-labelledby', () => { - const params = checkSetup( - '' + - '' + - 'aria label' - ); + const params = checkSetup(html` + + + aria label + `); assert.isFalse(checkEvaluate.apply(checkContext, params)); }); @@ -65,22 +66,22 @@ describe('explicit-label', () => { }); it('includes the `explicitLabel` text of the first non-empty label', () => { - const params = checkSetup( - '' + - '' + - '' + - '' - ); + const params = checkSetup(html` + + + + + `); checkEvaluate.apply(checkContext, params); assert.deepEqual(checkContext._data, { explicitLabel: 'text' }); }); it('is empty { explicitLabel: "" } if the label is empty', () => { - const params = checkSetup( - '' + - '' + - '' - ); + const params = checkSetup(html` + + + + `); checkEvaluate.apply(checkContext, params); assert.deepEqual(checkContext._data, { explicitLabel: '' }); }); @@ -94,13 +95,13 @@ describe('explicit-label', () => { }); it('includes each associated label', () => { - const params = checkSetup( - '' + - '' + - '' - ); + const params = checkSetup(html` + + + + `); checkEvaluate.apply(checkContext, params); - const ids = checkContext._relatedNodes.map(node => '#' + node.id); + const ids = checkContext._relatedNodes.map(node => `#${node.id}`); assert.deepEqual(ids, ['#lbl1', '#lbl2']); }); }); diff --git a/test/checks/label/help-same-as-label.js b/test/checks/label/help-same-as-label.js index 8361c91bc..e3ba0f8fe 100644 --- a/test/checks/label/help-same-as-label.js +++ b/test/checks/label/help-same-as-label.js @@ -1,15 +1,13 @@ -describe('help-same-as-label', function () { - 'use strict'; +describe('help-same-as-label', () => { + const fixture = document.getElementById('fixture'); - var fixture = document.getElementById('fixture'); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; axe._tree = undefined; }); - it('should return true if an element has a label and a title with the same text', function () { - var node = document.createElement('input'); + it('should return true if an element has a label and a title with the same text', () => { + const node = document.createElement('input'); node.type = 'text'; node.title = 'Duplicate'; node.setAttribute('aria-label', 'Duplicate'); @@ -25,12 +23,12 @@ describe('help-same-as-label', function () { ); }); - it('should return true if an element has a label and aria-describedby with the same text', function () { - var node = document.createElement('input'); + it('should return true if an element has a label and aria-describedby with the same text', () => { + const node = document.createElement('input'); node.type = 'text'; node.setAttribute('aria-label', 'Duplicate'); node.setAttribute('aria-describedby', 'dby'); - var dby = document.createElement('div'); + const dby = document.createElement('div'); dby.id = 'dby'; dby.innerHTML = 'Duplicate'; @@ -47,8 +45,8 @@ describe('help-same-as-label', function () { ); }); - it('should return false if input only has a title', function () { - var node = document.createElement('input'); + it('should return false if input only has a title', () => { + const node = document.createElement('input'); node.type = 'text'; node.title = 'Duplicate'; @@ -64,11 +62,11 @@ describe('help-same-as-label', function () { ); }); - it('should return true if an input only has aria-describedby', function () { - var node = document.createElement('input'); + it('should return true if an input only has aria-describedby', () => { + const node = document.createElement('input'); node.type = 'text'; node.setAttribute('aria-describedby', 'dby'); - var dby = document.createElement('div'); + const dby = document.createElement('div'); dby.id = 'dby'; dby.innerHTML = 'Duplicate'; diff --git a/test/checks/label/hidden-explicit-label.js b/test/checks/label/hidden-explicit-label.js index dfb87f059..92cf81a15 100644 --- a/test/checks/label/hidden-explicit-label.js +++ b/test/checks/label/hidden-explicit-label.js @@ -1,18 +1,15 @@ -describe('hidden-explicit-label', function () { - 'use strict'; +describe('hidden-explicit-label', () => { + const shadowCheckSetup = axe.testUtils.shadowCheckSetup; + const checkContext = axe.testUtils.MockCheckContext(); + const checkSetup = axe.testUtils.checkSetup; + const check = checks['hidden-explicit-label']; - var shadowSupport = axe.testUtils.shadowSupport; - var shadowCheckSetup = axe.testUtils.shadowCheckSetup; - var checkContext = axe.testUtils.MockCheckContext(); - var checkSetup = axe.testUtils.checkSetup; - var check = checks['hidden-explicit-label']; - - afterEach(function () { + afterEach(() => { checkContext.reset(); }); - it('should return true if a hidden non-empty label is present', function () { - var args = checkSetup( + it('should return true if a hidden non-empty label is present', () => { + const args = checkSetup( '', {}, '#target' @@ -20,60 +17,54 @@ describe('hidden-explicit-label', function () { assert.isTrue(check.evaluate.apply(check, args)); }); - it('should return false if a visible non-empty label is present', function () { - var args = checkSetup( + it('should return false if a visible non-empty label is present', () => { + const args = checkSetup( '' ); assert.isFalse(check.evaluate.apply(check, args)); }); - it('should return true if an invisible empty label is present', function () { - var args = checkSetup( + it('should return true if an invisible empty label is present', () => { + const args = checkSetup( '' ); assert.isTrue(check.evaluate.apply(check, args)); }); - (shadowSupport.v1 ? it : xit)( - 'should return true if content is inside of shadow DOM', - function () { - var params = shadowCheckSetup( - '
', - '' - ); + it('should return true if content is inside of shadow DOM', () => { + const params = shadowCheckSetup( + '
', + '' + ); - assert.isTrue(check.evaluate.apply(shadowCheckSetup, params)); - } - ); + assert.isTrue(check.evaluate.apply(shadowCheckSetup, params)); + }); - (shadowSupport.v1 ? it : xit)( - 'should return false if part of the pairing is inside of shadow DOM', - function () { - var params = shadowCheckSetup( - '
', - '' - ); + it('should return false if part of the pairing is inside of shadow DOM', () => { + const params = shadowCheckSetup( + '
', + '' + ); - assert.isFalse(check.evaluate.apply(shadowCheckSetup, params)); - } - ); + assert.isFalse(check.evaluate.apply(shadowCheckSetup, params)); + }); - it('should fail when the label has aria-hidden=true', function () { - var html = ''; + it('should fail when the label has aria-hidden=true', () => { + let html = ''; html += '
'; html += ' '; html += ' '; html += '
'; - var args = checkSetup(html, {}, '#target'); + const args = checkSetup(html, {}, '#target'); assert.isTrue(check.evaluate.apply(check, args)); }); - describe('if the label is hidden', function () { - describe('and the element has an accessible name', function () { - it('should not fail', function () { - var html = ''; + describe('if the label is hidden', () => { + describe('and the element has an accessible name', () => { + it('should not fail', () => { + let html = ''; html += '
'; html += '
'; - var args = checkSetup(html, {}, '#target'); + const args = checkSetup(html, {}, '#target'); assert.isFalse(check.evaluate.apply(check, args)); }); }); }); - describe('SerialVirtualNode', function () { - it('should return false if no id', function () { - var vNode = new axe.SerialVirtualNode({ + describe('SerialVirtualNode', () => { + it('should return false if no id', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'input', attributes: { type: 'text' @@ -101,8 +92,8 @@ describe('hidden-explicit-label', function () { ); }); - it('should return undefined if it has id', function () { - var vNode = new axe.SerialVirtualNode({ + it('should return undefined if it has id', () => { + const vNode = new axe.SerialVirtualNode({ nodeName: 'input', attributes: { type: 'text', diff --git a/test/checks/label/implicit.js b/test/checks/label/implicit.js index 83eae364b..0df5f8b4d 100644 --- a/test/checks/label/implicit.js +++ b/test/checks/label/implicit.js @@ -78,7 +78,7 @@ describe('implicit-label', () => { '' ); checkEvaluate.apply(checkContext, params); - const ids = checkContext._relatedNodes.map(node => '#' + node.id); + const ids = checkContext._relatedNodes.map(node => `#${node.id}`); assert.deepEqual(ids, ['#lbl']); }); }); diff --git a/test/checks/label/label-content-name-mismatch.js b/test/checks/label/label-content-name-mismatch.js index 76b25bdb5..811867c50 100644 --- a/test/checks/label/label-content-name-mismatch.js +++ b/test/checks/label/label-content-name-mismatch.js @@ -1,189 +1,189 @@ -describe('label-content-name-mismatch tests', function () { - 'use strict'; +describe('label-content-name-mismatch tests', () => { + const html = axe.testUtils.html; - var queryFixture = axe.testUtils.queryFixture; - var check = checks['label-content-name-mismatch']; - var options = undefined; + const queryFixture = axe.testUtils.queryFixture; + const check = checks['label-content-name-mismatch']; + const options = undefined; - var fontApiSupport = !!document.fonts; + const fontApiSupport = !!document.fonts; - before(function (done) { + before(done => { if (!fontApiSupport) { done(); } - var materialFont = new FontFace( + const materialFont = new FontFace( 'Material Icons', 'url(https://fonts.gstatic.com/s/materialicons/v48/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2)' ); - materialFont.load().then(function () { + materialFont.load().then(() => { document.fonts.add(materialFont); done(); }); }); - it('returns true when visible text and accessible name (`aria-label`) matches (text sanitized)', function () { - var vNode = queryFixture( + it('returns true when visible text and accessible name (`aria-label`) matches (text sanitized)', () => { + const vNode = queryFixture( '
next page
' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isTrue(actual); }); - it('returns true when visible text and accessible name (`aria-label`) matches (character insensitive)', function () { - var vNode = queryFixture( + it('returns true when visible text and accessible name (`aria-label`) matches (character insensitive)', () => { + const vNode = queryFixture( '
next pAge
' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isTrue(actual); }); - it('returns true when visible text and accessible name (`aria-labelledby`) matches (character insensitive & text sanitized)', function () { - var vNode = queryFixture( - '
UNTIL THE VeRy EnD
' + - '
uNtIl the very end  
' - ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + it('returns true when visible text and accessible name (`aria-labelledby`) matches (character insensitive & text sanitized)', () => { + const vNode = queryFixture(html` +
UNTIL THE VeRy EnD
+
uNtIl the very end  
+ `); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isTrue(actual); }); - it('returns true when visible text is contained in the accessible name', function () { - var vNode = queryFixture( + it('returns true when visible text is contained in the accessible name', () => { + const vNode = queryFixture( '' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isTrue(actual); }); - it('returns false when visible text doesn’t match accessible name', function () { - var vNode = queryFixture( + it('returns false when visible text doesn’t match accessible name', () => { + const vNode = queryFixture( '
Next
' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isFalse(actual); }); - it('returns false when not all of visible text is included in accessible name', function () { - var vNode = queryFixture( + it('returns false when not all of visible text is included in accessible name', () => { + const vNode = queryFixture( '' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isFalse(actual); }); - it('returns false when element has non-matching accessible name (`aria-labelledby`) and text content', function () { - var vNode = queryFixture( - '
some content
' + - '
123
' - ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + it('returns false when element has non-matching accessible name (`aria-labelledby`) and text content', () => { + const vNode = queryFixture(html` +
some content
+
123
+ `); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isFalse(actual); }); - it('returns true when visible text excluding emoji is part of accessible name', function () { - var vNode = queryFixture( + it('returns true when visible text excluding emoji is part of accessible name', () => { + const vNode = queryFixture( '' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isTrue(actual); }); - it('returns true when visible text excluding punctuations/ symbols is part of accessible name', function () { - var vNode = queryFixture( + it('returns true when visible text excluding punctuations/ symbols is part of accessible name', () => { + const vNode = queryFixture( '' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isTrue(actual); }); (fontApiSupport ? it : it.skip)( 'returns true when visible text excluding ligature icon is part of accessible name', - function () { - var vNode = queryFixture( + () => { + const vNode = queryFixture( '' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isTrue(actual); } ); - it('returns true when visible text excluding private use unicode is part of accessible name', function () { - var vNode = queryFixture( + it('returns true when visible text excluding private use unicode is part of accessible name', () => { + const vNode = queryFixture( '' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isTrue(actual); }); - it('returns undefined (needs review) when visible text name is only an emoji', function () { - var vNode = queryFixture( + it('returns undefined (needs review) when visible text name is only an emoji', () => { + const vNode = queryFixture( '' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isUndefined(actual); }); - it('returns undefined (needs review) when accessible name is an emoji', function () { - var vNode = queryFixture( + it('returns undefined (needs review) when accessible name is an emoji', () => { + const vNode = queryFixture( '' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isUndefined(actual); }); - it('returns undefined (needs review) for visible text is single characters (punctuation) used as icon', function () { - var vNode = queryFixture( + it('returns undefined (needs review) for visible text is single characters (punctuation) used as icon', () => { + const vNode = queryFixture( '' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isUndefined(actual); }); - it('returns undefined (needs review) for unicode as accessible name and text content', function () { - var vNode = queryFixture( + it('returns undefined (needs review) for unicode as accessible name and text content', () => { + const vNode = queryFixture( '' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isUndefined(actual); }); - it('returns undefined (needs review) for unicode text content', function () { - var vNode = queryFixture( + it('returns undefined (needs review) for unicode text content', () => { + const vNode = queryFixture( '' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isUndefined(actual); }); - it('returns undefined (needs review) when punctuation is used as text content', function () { - var vNode = queryFixture( + it('returns undefined (needs review) when punctuation is used as text content', () => { + const vNode = queryFixture( '' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isUndefined(actual); }); - it('returns true when normal text content which is punctuated', function () { - var vNode = queryFixture( + it('returns true when normal text content which is punctuated', () => { + const vNode = queryFixture( '' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isTrue(actual); }); - it('returns false when normal puntuated text content is not contained in accessible name is punctuated', function () { - var vNode = queryFixture( + it('returns false when normal puntuated text content is not contained in accessible name is punctuated', () => { + const vNode = queryFixture( '' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isFalse(actual); }); - it('returns true when text contains
', function () { - var vNode = queryFixture( + it('returns true when text contains
', () => { + const vNode = queryFixture( '' ); - var actual = check.evaluate(vNode.actualNode, options, vNode); + const actual = check.evaluate(vNode.actualNode, options, vNode); assert.isTrue(actual); }); }); diff --git a/test/checks/label/multiple-label.js b/test/checks/label/multiple-label.js index 3e40cb97d..8f7f6169d 100644 --- a/test/checks/label/multiple-label.js +++ b/test/checks/label/multiple-label.js @@ -1,22 +1,21 @@ -describe('multiple-label', function () { - 'use strict'; +describe('multiple-label', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var shadowSupported = axe.testUtils.shadowSupport.v1; - var checkContext = axe.testUtils.MockCheckContext(); - var fixtureSetup = axe.testUtils.fixtureSetup; + const fixture = document.getElementById('fixture'); + const checkContext = axe.testUtils.MockCheckContext(); + const fixtureSetup = axe.testUtils.fixtureSetup; - afterEach(function () { + afterEach(() => { checkContext.reset(); }); - it('should return undefined if there are multiple implicit labels', function () { + it('should return undefined if there are multiple implicit labels', () => { fixtureSetup( '' ); - var target = fixture.querySelector('#target'); - var l1 = fixture.querySelector('#l1'); - var l2 = fixture.querySelector('#l2'); + const target = fixture.querySelector('#target'); + const l1 = fixture.querySelector('#l1'); + const l2 = fixture.querySelector('#l2'); assert.isUndefined( axe.testUtils .getCheckEvaluate('multiple-label') @@ -25,10 +24,10 @@ describe('multiple-label', function () { assert.deepEqual(checkContext._relatedNodes, [l1, l2]); }); - it('should return false if there is only one implicit label', function () { + it('should return false if there is only one implicit label', () => { fixtureSetup(''); - var target = fixture.querySelector('#target'); - var l1 = fixture.querySelector('#l1'); + const target = fixture.querySelector('#target'); + const l1 = fixture.querySelector('#l1'); assert.isFalse( axe.testUtils .getCheckEvaluate('multiple-label') @@ -37,17 +36,17 @@ describe('multiple-label', function () { assert.deepEqual(checkContext._relatedNodes, [l1]); }); - it('should return undefined if there are multiple explicit labels', function () { - fixtureSetup( - '' + - '' + - '' + - '' - ); - var target = fixture.querySelector('#target'); - var l1 = fixture.querySelector('#l1'); - var l2 = fixture.querySelector('#l2'); - var l3 = fixture.querySelector('#l3'); + it('should return undefined if there are multiple explicit labels', () => { + fixtureSetup(html` + + + + + `); + const target = fixture.querySelector('#target'); + const l1 = fixture.querySelector('#l1'); + const l2 = fixture.querySelector('#l2'); + const l3 = fixture.querySelector('#l3'); assert.isUndefined( axe.testUtils .getCheckEvaluate('multiple-label') @@ -56,12 +55,12 @@ describe('multiple-label', function () { assert.deepEqual(checkContext._relatedNodes, [l1, l2, l3]); }); - it('should return false if there is only one explicit label', function () { + it('should return false if there is only one explicit label', () => { fixtureSetup( '' ); - var target = fixture.querySelector('#target'); - var l1 = fixture.querySelector('#l1'); + const target = fixture.querySelector('#target'); + const l1 = fixture.querySelector('#l1'); assert.isFalse( axe.testUtils .getCheckEvaluate('multiple-label') @@ -70,14 +69,14 @@ describe('multiple-label', function () { assert.deepEqual(checkContext._relatedNodes, [l1]); }); - it('should return false if there are multiple explicit labels but one is hidden', function () { - fixtureSetup( - '' + - '' + - '' - ); - var target = fixture.querySelector('#test-input2'); - var l1 = fixture.querySelector('#l1'); + it('should return false if there are multiple explicit labels but one is hidden', () => { + fixtureSetup(html` + + + + `); + const target = fixture.querySelector('#test-input2'); + const l1 = fixture.querySelector('#l1'); assert.isFalse( axe.testUtils .getCheckEvaluate('multiple-label') @@ -86,13 +85,13 @@ describe('multiple-label', function () { assert.deepEqual(checkContext._relatedNodes, [l1]); }); - it('should return undefined if there are multiple implicit labels and one is visually hidden', function () { + it('should return undefined if there are multiple implicit labels and one is visually hidden', () => { fixtureSetup( '' ); - var target = fixture.querySelector('#target'); - var l1 = fixture.querySelector('#l1'); - var l2 = fixture.querySelector('#l2'); + const target = fixture.querySelector('#target'); + const l1 = fixture.querySelector('#l1'); + const l2 = fixture.querySelector('#l2'); assert.isUndefined( axe.testUtils .getCheckEvaluate('multiple-label') @@ -101,16 +100,16 @@ describe('multiple-label', function () { assert.deepEqual(checkContext._relatedNodes, [l1, l2]); }); - it('should return undefined if there are multiple explicit labels but some are hidden', function () { - fixtureSetup( - '' + - '' + - '' + - '' - ); - var target = fixture.querySelector('#me'); - var l1 = fixture.querySelector('#l1'); - var l3 = fixture.querySelector('#l3'); + it('should return undefined if there are multiple explicit labels but some are hidden', () => { + fixtureSetup(html` + + + + + `); + const target = fixture.querySelector('#me'); + const l1 = fixture.querySelector('#l1'); + const l3 = fixture.querySelector('#l3'); assert.isUndefined( axe.testUtils .getCheckEvaluate('multiple-label') @@ -119,15 +118,15 @@ describe('multiple-label', function () { assert.deepEqual(checkContext._relatedNodes, [l1, l3]); }); - it('should return undefined if there are multiple explicit labels and one is visually hidden', function () { - fixtureSetup( - '' + - '' + - '' - ); - var target = fixture.querySelector('#me'); - var l1 = fixture.querySelector('#l1'); - var l2 = fixture.querySelector('#l2'); + it('should return undefined if there are multiple explicit labels and one is visually hidden', () => { + fixtureSetup(html` + + + + `); + const target = fixture.querySelector('#me'); + const l1 = fixture.querySelector('#l1'); + const l2 = fixture.querySelector('#l2'); assert.isUndefined( axe.testUtils .getCheckEvaluate('multiple-label') @@ -136,15 +135,15 @@ describe('multiple-label', function () { assert.deepEqual(checkContext._relatedNodes, [l1, l2]); }); - it('should return undefined if there are multiple explicit labels and one is screen reader hidden', function () { - fixtureSetup( - '' + - '' + - '' - ); - var target = fixture.querySelector('#me'); - var l1 = fixture.querySelector('#l1'); - var l2 = fixture.querySelector('#l2'); + it('should return undefined if there are multiple explicit labels and one is screen reader hidden', () => { + fixtureSetup(html` + + + + `); + const target = fixture.querySelector('#me'); + const l1 = fixture.querySelector('#l1'); + const l2 = fixture.querySelector('#l2'); assert.isUndefined( axe.testUtils .getCheckEvaluate('multiple-label') @@ -153,13 +152,13 @@ describe('multiple-label', function () { assert.deepEqual(checkContext._relatedNodes, [l1, l2]); }); - it('should return undefined if there are implicit and explicit labels', function () { + it('should return undefined if there are implicit and explicit labels', () => { fixtureSetup( '' ); - var target = fixture.querySelector('#target'); - var l1 = fixture.querySelector('#l1'); - var l2 = fixture.querySelector('#l2'); + const target = fixture.querySelector('#target'); + const l1 = fixture.querySelector('#l1'); + const l2 = fixture.querySelector('#l2'); assert.isUndefined( axe.testUtils .getCheckEvaluate('multiple-label') @@ -168,11 +167,11 @@ describe('multiple-label', function () { assert.deepEqual(checkContext._relatedNodes, [l1, l2]); }); - it('should return false if there an implicit label uses for attribute', function () { + it('should return false if there an implicit label uses for attribute', () => { fixtureSetup( '' ); - var target = fixture.querySelector('#target'); + const target = fixture.querySelector('#target'); assert.isFalse( axe.testUtils .getCheckEvaluate('multiple-label') @@ -180,17 +179,17 @@ describe('multiple-label', function () { ); }); - it('should return undefined given multiple labels and no aria-labelledby', function () { - fixtureSetup( - '' + - '' + - '' + - '' + - '' + - '' + - '' - ); - var target = fixture.querySelector('#A'); + it('should return undefined given multiple labels and no aria-labelledby', () => { + fixtureSetup(html` + + + + + + + + `); + const target = fixture.querySelector('#A'); assert.isUndefined( axe.testUtils .getCheckEvaluate('multiple-label') @@ -198,17 +197,17 @@ describe('multiple-label', function () { ); }); - it('should return undefined given multiple labels, one label AT visible, and no aria-labelledby', function () { - fixtureSetup( - '' + - '' + - '' + - '' + - '' + - '' + - '' - ); - var target = fixture.querySelector('#B'); + it('should return undefined given multiple labels, one label AT visible, and no aria-labelledby', () => { + fixtureSetup(html` + + + + + + + + `); + const target = fixture.querySelector('#B'); assert.isUndefined( axe.testUtils .getCheckEvaluate('multiple-label') @@ -216,13 +215,13 @@ describe('multiple-label', function () { ); }); - it('should return false given multiple labels, one label AT visible, and aria-labelledby for AT visible', function () { - fixtureSetup( - '' + - '' + - '' - ); - var target = fixture.querySelector('#D'); + it('should return false given multiple labels, one label AT visible, and aria-labelledby for AT visible', () => { + fixtureSetup(html` + + + + `); + const target = fixture.querySelector('#D'); assert.isFalse( axe.testUtils .getCheckEvaluate('multiple-label') @@ -230,13 +229,13 @@ describe('multiple-label', function () { ); }); - it('should return false given multiple labels, one label AT visible, and aria-labelledby for all', function () { - fixtureSetup( - '' + - '' + - '' - ); - var target = fixture.querySelector('#F'); + it('should return false given multiple labels, one label AT visible, and aria-labelledby for all', () => { + fixtureSetup(html` + + + + `); + const target = fixture.querySelector('#F'); assert.isFalse( axe.testUtils .getCheckEvaluate('multiple-label') @@ -244,13 +243,13 @@ describe('multiple-label', function () { ); }); - it('should return false given multiple labels, one label visible, and no aria-labelledby', function () { - fixtureSetup( - '' + - '' + - '' - ); - var target = fixture.querySelector('#I'); + it('should return false given multiple labels, one label visible, and no aria-labelledby', () => { + fixtureSetup(html` + + + + `); + const target = fixture.querySelector('#I'); assert.isFalse( axe.testUtils .getCheckEvaluate('multiple-label') @@ -258,17 +257,17 @@ describe('multiple-label', function () { ); }); - it('should return undefined given multiple labels, all visible, aria-labelledby for all', function () { - fixtureSetup( - '' + - '' + - '' + - '' + - '' + - '' + - '' - ); - var target = fixture.querySelector('#J'); + it('should return undefined given multiple labels, all visible, aria-labelledby for all', () => { + fixtureSetup(html` + + + + + + + + `); + const target = fixture.querySelector('#J'); assert.isUndefined( axe.testUtils .getCheckEvaluate('multiple-label') @@ -276,13 +275,13 @@ describe('multiple-label', function () { ); }); - it('should return undefined given multiple labels, one AT visible, no aria-labelledby', function () { - fixtureSetup( - '' + - '' + - '' - ); - var target = fixture.querySelector('#Q'); + it('should return undefined given multiple labels, one AT visible, no aria-labelledby', () => { + fixtureSetup(html` + + + + `); + const target = fixture.querySelector('#Q'); assert.isUndefined( axe.testUtils .getCheckEvaluate('multiple-label') @@ -290,61 +289,52 @@ describe('multiple-label', function () { ); }); - (shadowSupported ? it : xit)( - 'should consider labels in the same document/shadow tree', - function () { - fixture.innerHTML = '
'; - var target = document.querySelector('#target'); - var shadowRoot = target.attachShadow({ mode: 'open' }); - shadowRoot.innerHTML = - ''; - var shadowTarget = target.shadowRoot; - fixtureSetup(); - assert.isFalse( - axe.testUtils - .getCheckEvaluate('multiple-label') - .call(checkContext, shadowTarget.firstElementChild) - ); - } - ); + it('should consider labels in the same document/shadow tree', () => { + fixture.innerHTML = '
'; + const target = document.querySelector('#target'); + const shadowRoot = target.attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = + ''; + const shadowTarget = target.shadowRoot; + fixtureSetup(); + assert.isFalse( + axe.testUtils + .getCheckEvaluate('multiple-label') + .call(checkContext, shadowTarget.firstElementChild) + ); + }); - (shadowSupported ? it : xit)( - 'should return false for valid multiple labels in the same document/shadow tree', - function () { - fixture.innerHTML = '
'; - var target = document.querySelector('#target'); - var shadowRoot = target.attachShadow({ mode: 'open' }); - var innerHTML = ''; - innerHTML += ''; - innerHTML += ''; - shadowRoot.innerHTML = innerHTML; - fixtureSetup(); - var shadowTarget = target.shadowRoot; - assert.isFalse( - axe.testUtils - .getCheckEvaluate('multiple-label') - .call(checkContext, shadowTarget.firstElementChild) - ); - } - ); + it('should return false for valid multiple labels in the same document/shadow tree', () => { + fixture.innerHTML = '
'; + const target = document.querySelector('#target'); + const shadowRoot = target.attachShadow({ mode: 'open' }); + let innerHTML = ''; + innerHTML += ''; + innerHTML += ''; + shadowRoot.innerHTML = innerHTML; + fixtureSetup(); + const shadowTarget = target.shadowRoot; + assert.isFalse( + axe.testUtils + .getCheckEvaluate('multiple-label') + .call(checkContext, shadowTarget.firstElementChild) + ); + }); - (shadowSupported ? it : xit)( - 'should return undefined for invalid multiple labels in the same document/shadow tree', - function () { - fixture.innerHTML = '
'; - var target = document.querySelector('#target'); - var shadowRoot = target.attachShadow({ mode: 'open' }); - var innerHTML = ''; - innerHTML += ''; - innerHTML += ''; - shadowRoot.innerHTML = innerHTML; - fixtureSetup(); - var shadowTarget = target.shadowRoot; - assert.isUndefined( - axe.testUtils - .getCheckEvaluate('multiple-label') - .call(checkContext, shadowTarget.firstElementChild) - ); - } - ); + it('should return undefined for invalid multiple labels in the same document/shadow tree', () => { + fixture.innerHTML = '
'; + const target = document.querySelector('#target'); + const shadowRoot = target.attachShadow({ mode: 'open' }); + let innerHTML = ''; + innerHTML += ''; + innerHTML += ''; + shadowRoot.innerHTML = innerHTML; + fixtureSetup(); + const shadowTarget = target.shadowRoot; + assert.isUndefined( + axe.testUtils + .getCheckEvaluate('multiple-label') + .call(checkContext, shadowTarget.firstElementChild) + ); + }); }); diff --git a/test/checks/label/title-only.js b/test/checks/label/title-only.js index e1439650c..46a607b5f 100644 --- a/test/checks/label/title-only.js +++ b/test/checks/label/title-only.js @@ -1,15 +1,13 @@ -describe('title-only', function () { - 'use strict'; +describe('title-only', () => { + const fixture = document.getElementById('fixture'); - var fixture = document.getElementById('fixture'); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; axe._tree = undefined; }); - it('should return true if an element only has a title', function () { - var node = document.createElement('input'); + it('should return true if an element only has a title', () => { + const node = document.createElement('input'); node.type = 'text'; node.title = 'Duplicate'; @@ -34,11 +32,11 @@ describe('title-only', function () { ); }); - it('should return true if an element only has aria-describedby', function () { - var node = document.createElement('input'); + it('should return true if an element only has aria-describedby', () => { + const node = document.createElement('input'); node.type = 'text'; node.setAttribute('aria-describedby', 'dby'); - var dby = document.createElement('div'); + const dby = document.createElement('div'); dby.id = 'dby'; dby.innerHTML = 'woop'; diff --git a/test/checks/landmarks/landmark-is-unique-after.js b/test/checks/landmarks/landmark-is-unique-after.js index 81eb404d3..ae9716808 100644 --- a/test/checks/landmarks/landmark-is-unique-after.js +++ b/test/checks/landmarks/landmark-is-unique-after.js @@ -1,7 +1,5 @@ -describe('landmark-is-unique-after', function () { - 'use strict'; - - var checkContext = axe.testUtils.MockCheckContext(); +describe('landmark-is-unique-after', () => { + const checkContext = axe.testUtils.MockCheckContext(); function createResult(result, data) { return { result: result, @@ -21,13 +19,13 @@ describe('landmark-is-unique-after', function () { }); } - afterEach(function () { + afterEach(() => { axe._tree = undefined; checkContext.reset(); }); - it('should update duplicate landmarks with failed result', function () { - var result = checks['landmark-is-unique'].after([ + it('should update duplicate landmarks with failed result', () => { + const result = checks['landmark-is-unique'].after([ createResultWithSameRelatedNodes(true, { role: 'some role', accessibleText: 'some accessibleText' @@ -46,7 +44,7 @@ describe('landmark-is-unique-after', function () { }) ]); - var expectedResult = [ + const expectedResult = [ createResultWithProvidedRelatedNodes( false, { diff --git a/test/checks/landmarks/landmark-is-unique.js b/test/checks/landmarks/landmark-is-unique.js index 9351b3d53..6e10c9c55 100644 --- a/test/checks/landmarks/landmark-is-unique.js +++ b/test/checks/landmarks/landmark-is-unique.js @@ -1,29 +1,27 @@ -describe('landmark-is-unique', function () { - 'use strict'; +describe('landmark-is-unique', () => { + const checkContext = new axe.testUtils.MockCheckContext(); + let fixture; + let axeFixtureSetup; - var checkContext = new axe.testUtils.MockCheckContext(); - var fixture; - var axeFixtureSetup; - - beforeEach(function () { + beforeEach(() => { fixture = document.getElementById('fixture'); axeFixtureSetup = axe.testUtils.fixtureSetup; }); - afterEach(function () { + afterEach(() => { axe._tree = undefined; checkContext.reset(); }); - it('should return true, with correct role and no accessible text', function () { + it('should return true, with correct role and no accessible text', () => { axeFixtureSetup('
test
'); - var node = fixture.querySelector('div'); - var expectedData = { + const node = fixture.querySelector('div'); + const expectedData = { accessibleText: null, role: 'main' }; axe._tree = axe.utils.getFlattenedTree(fixture); - var virtualNode = axe.utils.getNodeFromTree(axe._tree[0], node); + const virtualNode = axe.utils.getNodeFromTree(axe._tree[0], node); assert.isTrue( axe.testUtils .getCheckEvaluate('landmark-is-unique') @@ -33,15 +31,15 @@ describe('landmark-is-unique', function () { assert.deepEqual(checkContext._relatedNodes, [node]); }); - it('should return true, with correct role and the accessible text lowercased', function () { + it('should return true, with correct role and the accessible text lowercased', () => { axeFixtureSetup('
test
'); - var node = fixture.querySelector('div'); - var expectedData = { + const node = fixture.querySelector('div'); + const expectedData = { accessibleText: 'test text', role: 'main' }; axe._tree = axe.utils.getFlattenedTree(fixture); - var virtualNode = axe.utils.getNodeFromTree(axe._tree[0], node); + const virtualNode = axe.utils.getNodeFromTree(axe._tree[0], node); assert.isTrue( axe.testUtils .getCheckEvaluate('landmark-is-unique') diff --git a/test/checks/language/has-lang.js b/test/checks/language/has-lang.js index 7d8c18c62..5e4df0b0b 100644 --- a/test/checks/language/has-lang.js +++ b/test/checks/language/has-lang.js @@ -1,53 +1,51 @@ -describe('has-lang', function () { - 'use strict'; +describe('has-lang', () => { + const fixture = document.getElementById('fixture'); + const checkContext = axe.testUtils.MockCheckContext(); + const checkSetup = axe.testUtils.checkSetup; + const hasLangEvaluate = axe.testUtils.getCheckEvaluate('has-lang'); - var fixture = document.getElementById('fixture'); - var checkContext = axe.testUtils.MockCheckContext(); - var checkSetup = axe.testUtils.checkSetup; - var hasLangEvaluate = axe.testUtils.getCheckEvaluate('has-lang'); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; checkContext.reset(); }); - it('should return true if a lang attribute is present', function () { - var params = checkSetup('
'); + it('should return true if a lang attribute is present', () => { + const params = checkSetup('
'); assert.isTrue(hasLangEvaluate.apply(checkContext, params)); }); - it('should return false if only `xml:lang` attribute is present', function () { - var params = checkSetup('
'); + it('should return false if only `xml:lang` attribute is present', () => { + const params = checkSetup('
'); assert.isFalse(hasLangEvaluate.apply(checkContext, params)); assert.equal(checkContext._data.messageKey, 'noXHTML'); }); - it('should return true if both `lang` and `xml:lang` attribute is present', function () { - var params = checkSetup( + it('should return true if both `lang` and `xml:lang` attribute is present', () => { + const params = checkSetup( '
' ); assert.isTrue(hasLangEvaluate.apply(checkContext, params)); }); - it('should return false if xml:lang and lang attributes are not present', function () { - var params = checkSetup('
'); + it('should return false if xml:lang and lang attributes are not present', () => { + const params = checkSetup('
'); assert.isFalse(hasLangEvaluate.apply(checkContext, params)); assert.equal(checkContext._data.messageKey, 'noLang'); }); - it('should return false if lang is left empty', function () { - var params = checkSetup('
'); + it('should return false if lang is left empty', () => { + const params = checkSetup('
'); assert.isFalse(hasLangEvaluate.apply(checkContext, params)); assert.equal(checkContext._data.messageKey, 'noLang'); }); - it('should support options.attributes', function () { - var params = checkSetup('
', { + it('should support options.attributes', () => { + const params = checkSetup('
', { attributes: ['foo'] }); diff --git a/test/checks/language/valid-lang.js b/test/checks/language/valid-lang.js index a42baa206..5a2a915d1 100644 --- a/test/checks/language/valid-lang.js +++ b/test/checks/language/valid-lang.js @@ -1,81 +1,82 @@ -describe('valid-lang', function () { - 'use strict'; +describe('valid-lang', () => { + const fixture = document.getElementById('fixture'); + const checkSetup = axe.testUtils.checkSetup; + const checkContext = axe.testUtils.MockCheckContext(); + const validLangEvaluate = axe.testUtils.getCheckEvaluate('valid-lang'); - var fixture = document.getElementById('fixture'); - var checkSetup = axe.testUtils.checkSetup; - var checkContext = axe.testUtils.MockCheckContext(); - var validLangEvaluate = axe.testUtils.getCheckEvaluate('valid-lang'); - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; checkContext.reset(); }); - it('should return false if a lang attribute is present in options', function () { - var params = checkSetup('
text
', { + it('should return false if a lang attribute is present in options', () => { + const params = checkSetup('
text
', { value: ['blah', 'blah', 'woohoo'] }); assert.isFalse(validLangEvaluate.apply(checkContext, params)); }); - it('should lowercase options and attribute first', function () { - var params = checkSetup('
text
', { + it('should lowercase options and attribute first', () => { + const params = checkSetup('
text
', { value: ['blah', 'blah', 'wOohoo'] }); assert.isFalse(validLangEvaluate.apply(checkContext, params)); }); - it('should return true if a lang attribute is not present in options', function () { - var params = checkSetup('
text
'); + it('should return true if a lang attribute is not present in options', () => { + const params = checkSetup('
text
'); assert.isTrue(validLangEvaluate.apply(checkContext, params)); assert.deepEqual(checkContext._data, ['lang="FOO"']); }); - it('should return false (and not throw) when given no present in options', function () { - var params = checkSetup('
text
'); + it('should return false (and not throw) when given no present in options', () => { + const params = checkSetup('
text
'); assert.isFalse(validLangEvaluate.apply(checkContext, params)); }); - it('should return true if the language is badly formatted', function () { - var params = checkSetup('
text
'); + it('should return true if the language is badly formatted', () => { + const params = checkSetup('
text
'); assert.isTrue(validLangEvaluate.apply(checkContext, params)); assert.deepEqual(checkContext._data, ['lang="en_US"']); }); - it('should return false if it matches a substring proceeded by -', function () { - var params = checkSetup('
text
'); + it('should return false if it matches a substring proceeded by -', () => { + const params = checkSetup('
text
'); assert.isFalse(validLangEvaluate.apply(checkContext, params)); }); - it('should work with xml:lang', function () { - var params = checkSetup('
text
'); + it('should work with xml:lang', () => { + const params = checkSetup('
text
'); assert.isFalse(validLangEvaluate.apply(checkContext, params)); }); - it('should accept options.attributes', function () { - var params = checkSetup('
text
', { - attributes: ['custom-lang'] - }); + it('should accept options.attributes', () => { + const params = checkSetup( + '
text
', + { + attributes: ['custom-lang'] + } + ); assert.isTrue(validLangEvaluate.apply(checkContext, params)); assert.deepEqual(checkContext._data, ['custom-lang="en_US"']); }); - it('should return true if lang value is just whitespace', function () { - var params = checkSetup('
text
'); + it('should return true if lang value is just whitespace', () => { + const params = checkSetup('
text
'); assert.isTrue(validLangEvaluate.apply(checkContext, params)); }); - it('should return false if a lang attribute element has no content', function () { - var params = checkSetup('
'); + it('should return false if a lang attribute element has no content', () => { + const params = checkSetup('
'); assert.isFalse(validLangEvaluate.apply(checkContext, params)); assert.deepEqual(checkContext._data, null); diff --git a/test/checks/language/xml-lang-mismatch.js b/test/checks/language/xml-lang-mismatch.js index 7c47213cf..0256aab94 100644 --- a/test/checks/language/xml-lang-mismatch.js +++ b/test/checks/language/xml-lang-mismatch.js @@ -1,14 +1,12 @@ -describe('xml-lang-mismatch', function () { - 'use strict'; +describe('xml-lang-mismatch', () => { + const checkContext = axe.testUtils.MockCheckContext(); + const queryFixture = axe.testUtils.queryFixture; - var checkContext = axe.testUtils.MockCheckContext(); - var queryFixture = axe.testUtils.queryFixture; - - beforeEach(function () { + beforeEach(() => { // using a div element (instead of html), as the check is agnostic of element type }); - afterEach(function () { + afterEach(() => { checkContext.reset(); }); @@ -16,8 +14,8 @@ describe('xml-lang-mismatch', function () { // hence below tests are only for HTML element, although the logic in the check looks for matches in value os lang and xml:lang // rather than node type match - hence the check can be re-used. - it('should return false if a only lang is supplied', function () { - var vNode = queryFixture('
'); + it('should return false if a only lang is supplied', () => { + const vNode = queryFixture('
'); assert.isFalse( axe.testUtils .getCheckEvaluate('xml-lang-mismatch') @@ -25,8 +23,8 @@ describe('xml-lang-mismatch', function () { ); }); - it('should return false if a only xml:lang is supplied albeit with region', function () { - var vNode = queryFixture('
'); + it('should return false if a only xml:lang is supplied albeit with region', () => { + const vNode = queryFixture('
'); assert.isFalse( axe.testUtils .getCheckEvaluate('xml-lang-mismatch') @@ -34,10 +32,10 @@ describe('xml-lang-mismatch', function () { ); }); - it('should return false if lang is undefined', function () { - var node = document.createElement('div'); + it('should return false if lang is undefined', () => { + const node = document.createElement('div'); node.setAttribute('lang', undefined); - var tree = axe.testUtils.flatTreeSetup(node); + const tree = axe.testUtils.flatTreeSetup(node); assert.isFalse( axe.testUtils .getCheckEvaluate('xml-lang-mismatch') @@ -45,8 +43,8 @@ describe('xml-lang-mismatch', function () { ); }); - it('should return true if lang and xml:lang is identical', function () { - var vNode = queryFixture( + it('should return true if lang and xml:lang is identical', () => { + const vNode = queryFixture( '
' ); assert.isTrue( @@ -56,8 +54,8 @@ describe('xml-lang-mismatch', function () { ); }); - it('should return true if lang and xml:lang have identical primary sub tag', function () { - var vNode = queryFixture( + it('should return true if lang and xml:lang have identical primary sub tag', () => { + const vNode = queryFixture( '
' ); assert.isTrue( @@ -67,11 +65,11 @@ describe('xml-lang-mismatch', function () { ); }); - it('should return false if lang and xml:lang are not identical', function () { - var vNode = queryFixture( + it('should return false if lang and xml:lang are not identical', () => { + const vNode = queryFixture( '
' ); - var actual = axe.testUtils + const actual = axe.testUtils .getCheckEvaluate('xml-lang-mismatch') .call(checkContext, null, {}, vNode); assert.isFalse(actual); diff --git a/test/checks/lists/dlitem.js b/test/checks/lists/dlitem.js index f9853dfd6..162bd9774 100644 --- a/test/checks/lists/dlitem.js +++ b/test/checks/lists/dlitem.js @@ -1,162 +1,136 @@ -describe('dlitem', function () { - 'use strict'; +describe('dlitem', () => { + const html = axe.testUtils.html; - var fixture = document.getElementById('fixture'); - var checkSetup = axe.testUtils.checkSetup; - var shadowSupport = axe.testUtils.shadowSupport; + const fixture = document.getElementById('fixture'); + const checkSetup = axe.testUtils.checkSetup; - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should pass if the dlitem has a parent
', function () { - var checkArgs = checkSetup('
My list item
'); + it('should pass if the dlitem has a parent
', () => { + const checkArgs = checkSetup('
My list item
'); assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs)); }); - it('should fail if the dt element has an incorrect parent', function () { - var checkArgs = checkSetup( + it('should fail if the dt element has an incorrect parent', () => { + const checkArgs = checkSetup( '' ); assert.isFalse(checks.dlitem.evaluate.apply(null, checkArgs)); }); - it('should pass if the dt element has a parent
with role="list"', function () { - var checkArgs = checkSetup( + it('should pass if the dt element has a parent
with role="list"', () => { + const checkArgs = checkSetup( '
My list item
' ); assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs)); }); - it('should pass if the dt element has a parent
with role="presentation"', function () { - var checkArgs = checkSetup( + it('should pass if the dt element has a parent
with role="presentation"', () => { + const checkArgs = checkSetup( '
My list item
' ); assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs)); }); - it('should fail if the dt element has a parent
with a changed role', function () { - var checkArgs = checkSetup( + it('should fail if the dt element has a parent
with a changed role', () => { + const checkArgs = checkSetup( '
My list item<
/dl>' ); assert.isFalse(checks.dlitem.evaluate.apply(null, checkArgs)); }); - it('should pass if the dt element has a parent
with an abstract role', function () { - var checkArgs = checkSetup( + it('should pass if the dt element has a parent
with an abstract role', () => { + const checkArgs = checkSetup( '
My list item
' ); assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs)); }); - it('should pass if the dt element has a parent
with an invalid role', function () { - var checkArgs = checkSetup( + it('should pass if the dt element has a parent
with an invalid role', () => { + const checkArgs = checkSetup( '
My list item
' ); assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs)); }); - it('should fail if the dt element has a parent
with a changed role', function () { - var checkArgs = checkSetup( + it('should fail if the dt element has a parent
with a changed role', () => { + const checkArgs = checkSetup( '
My list item
' ); assert.isFalse(checks.dlitem.evaluate.apply(null, checkArgs)); }); - it('returns true if the dd/dt is in a div with a dl as grandparent', function () { - var nodeNames = ['dd', 'dt']; - nodeNames.forEach(function (nodeName) { - var checkArgs = checkSetup( - '
<' + - nodeName + - ' id="target">My list item
' + it('returns true if the dd/dt is in a div with a dl as grandparent', () => { + const nodeNames = ['dd', 'dt']; + nodeNames.forEach(nodeName => { + const checkArgs = checkSetup( + html`
<${nodeName} id="target">My list item
` ); assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs)); }); }); - it('returns false if the dd/dt is in a div with a role with a dl as grandparent with a list role', function () { - var nodeNames = ['dd', 'dt']; - nodeNames.forEach(function (nodeName) { - var checkArgs = checkSetup( - '
<' + - nodeName + - ' id="target">My list item
' + it('returns false if the dd/dt is in a div with a role with a dl as grandparent with a list role', () => { + const nodeNames = ['dd', 'dt']; + nodeNames.forEach(nodeName => { + const checkArgs = checkSetup( + html`
<${nodeName} id="target">My list item
` ); assert.isFalse(checks.dlitem.evaluate.apply(null, checkArgs)); }); }); - it('returns false if the dd/dt is in a div[role=presentation] with a dl as grandparent', function () { - var nodeNames = ['dd', 'dt']; - nodeNames.forEach(function (nodeName) { - var checkArgs = checkSetup( - '
<' + - nodeName + - ' id="target">My list item
' + it('returns false if the dd/dt is in a div[role=presentation] with a dl as grandparent', () => { + const nodeNames = ['dd', 'dt']; + nodeNames.forEach(nodeName => { + const checkArgs = checkSetup( + html`
<${nodeName} id="target">My list item
` ); assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs)); }); }); - it('returns false if the dd/dt is in a div[role=none] with a dl as grandparent', function () { - var nodeNames = ['dd', 'dt']; - nodeNames.forEach(function (nodeName) { - var checkArgs = checkSetup( - '
<' + - nodeName + - ' id="target">My list item
' + it('returns false if the dd/dt is in a div[role=none] with a dl as grandparent', () => { + const nodeNames = ['dd', 'dt']; + nodeNames.forEach(nodeName => { + const checkArgs = checkSetup( + html`
<${nodeName} id="target">My list item
` ); assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs)); }); }); - (shadowSupport.v1 ? it : xit)( - 'should return true in a shadow DOM pass', - function () { - var node = document.createElement('div'); - node.innerHTML = '
My list item
'; - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = '
'; + it('should return true in a shadow DOM pass', () => { + const node = document.createElement('div'); + node.innerHTML = '
My list item
'; + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = '
'; - var checkArgs = checkSetup(node, 'dt'); - assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs)); - } - ); - - (shadowSupport.v1 ? it : xit)( - 'should return false in a shadow DOM fail', - function () { - var node = document.createElement('div'); - node.innerHTML = '
My list item
'; - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = '
'; - - var checkArgs = checkSetup(node, 'dt'); - assert.isFalse(checks.dlitem.evaluate.apply(null, checkArgs)); - } - ); - - (shadowSupport.v1 ? it : xit)( - 'should return true when the item is grouped in dl > div in a shadow DOM', - function () { - var node = document.createElement('div'); - node.innerHTML = '
My list item
'; - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = '
'; - - var checkArgs = checkSetup(node, 'dt'); - assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs)); - } - ); + const checkArgs = checkSetup(node, 'dt'); + assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs)); + }); + + it('should return false in a shadow DOM fail', () => { + const node = document.createElement('div'); + node.innerHTML = '
My list item
'; + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = '
'; + + const checkArgs = checkSetup(node, 'dt'); + assert.isFalse(checks.dlitem.evaluate.apply(null, checkArgs)); + }); + + it('should return true when the item is grouped in dl > div in a shadow DOM', () => { + const node = document.createElement('div'); + node.innerHTML = '
My list item
'; + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = '
'; + + const checkArgs = checkSetup(node, 'dt'); + assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs)); + }); }); diff --git a/test/checks/lists/listitem.js b/test/checks/lists/listitem.js index 24b6adeb5..28ded858a 100644 --- a/test/checks/lists/listitem.js +++ b/test/checks/lists/listitem.js @@ -1,118 +1,109 @@ -describe('listitem', function () { - 'use strict'; +describe('listitem', () => { + const checkContext = axe.testUtils.MockCheckContext(); + const checkSetup = axe.testUtils.checkSetup; + const fixtureSetup = axe.testUtils.fixtureSetup; + const checkEvaluate = axe.testUtils.getCheckEvaluate('listitem'); - var shadowSupport = axe.testUtils.shadowSupport; - var checkContext = axe.testUtils.MockCheckContext(); - var checkSetup = axe.testUtils.checkSetup; - var fixtureSetup = axe.testUtils.fixtureSetup; - var checkEvaluate = axe.testUtils.getCheckEvaluate('listitem'); - - afterEach(function () { + afterEach(() => { checkContext.reset(); }); - it('should pass if the listitem has a parent
    ', function () { - var params = checkSetup('
    1. My list item
    '); - var result = checkEvaluate.apply(checkContext, params); + it('should pass if the listitem has a parent
      ', () => { + const params = checkSetup('
      1. My list item
      '); + const result = checkEvaluate.apply(checkContext, params); assert.isTrue(result); }); - it('should pass if the listitem has a parent
        ', function () { - var params = checkSetup('
        • My list item
        '); - var result = checkEvaluate.apply(checkContext, params); + it('should pass if the listitem has a parent
          ', () => { + const params = checkSetup('
          • My list item
          '); + const result = checkEvaluate.apply(checkContext, params); assert.isTrue(result); }); - it('should pass if the listitem has a parent role=list', function () { - var params = checkSetup( + it('should pass if the listitem has a parent role=list', () => { + const params = checkSetup( '
        • My list item
        • ' ); - var result = checkEvaluate.apply(checkContext, params); + const result = checkEvaluate.apply(checkContext, params); assert.isTrue(result); }); - it('should pass if the listitem has a parent role=none', function () { - var params = checkSetup( + it('should pass if the listitem has a parent role=none', () => { + const params = checkSetup( '
          • My list item
          ' ); - var result = checkEvaluate.apply(checkContext, params); + const result = checkEvaluate.apply(checkContext, params); assert.isTrue(result); }); - it('should pass if the listitem has a parent role=presentation', function () { - var params = checkSetup( + it('should pass if the listitem has a parent role=presentation', () => { + const params = checkSetup( '
          • My list item
          ' ); - var result = checkEvaluate.apply(checkContext, params); + const result = checkEvaluate.apply(checkContext, params); assert.isTrue(result); }); - it('should fail if the listitem has an incorrect parent', function () { - var params = checkSetup('
        • My list item
        • '); - var result = checkEvaluate.apply(checkContext, params); + it('should fail if the listitem has an incorrect parent', () => { + const params = checkSetup('
        • My list item
        • '); + const result = checkEvaluate.apply(checkContext, params); assert.isFalse(result); }); - it('should fail if the listitem has a parent
            with changed role', function () { - var params = checkSetup( + it('should fail if the listitem has a parent
              with changed role', () => { + const params = checkSetup( '
              1. My list item
              ' ); - var result = checkEvaluate.apply(checkContext, params); + const result = checkEvaluate.apply(checkContext, params); assert.isFalse(result); assert.equal(checkContext._data.messageKey, 'roleNotValid'); }); - it('should pass if the listitem has a parent
                with an invalid role', function () { - var params = checkSetup( + it('should pass if the listitem has a parent
                  with an invalid role', () => { + const params = checkSetup( '
                  1. My list item
                  ' ); - var result = checkEvaluate.apply(checkContext, params); + const result = checkEvaluate.apply(checkContext, params); assert.isTrue(result); }); - it('should pass if the listitem has a parent
                    with an abstract role', function () { - var params = checkSetup( + it('should pass if the listitem has a parent
                      with an abstract role', () => { + const params = checkSetup( '
                      1. My list item
                      ' ); - var result = checkEvaluate.apply(checkContext, params); + const result = checkEvaluate.apply(checkContext, params); assert.isTrue(result); }); - (shadowSupport.v1 ? it : xit)( - 'should return true in a shadow DOM pass', - function () { - var node = document.createElement('div'); - node.innerHTML = '
                    1. My list item
                    2. '; - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = '
                      '; - fixtureSetup(node); - var target = node.querySelector('#target'); - var virtualTarget = axe.utils.getNodeFromTree(target); - var result = checkEvaluate.apply(checkContext, [ - target, - {}, - virtualTarget - ]); - assert.isTrue(result); - } - ); + it('should return true in a shadow DOM pass', () => { + const node = document.createElement('div'); + node.innerHTML = '
                    3. My list item
                    4. '; + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = '
                      '; + fixtureSetup(node); + const target = node.querySelector('#target'); + const virtualTarget = axe.utils.getNodeFromTree(target); + const result = checkEvaluate.apply(checkContext, [ + target, + {}, + virtualTarget + ]); + assert.isTrue(result); + }); - (shadowSupport.v1 ? it : xit)( - 'should return false in a shadow DOM fail', - function () { - var node = document.createElement('div'); - node.innerHTML = '
                    5. My list item
                    6. '; - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = '
                      '; - fixtureSetup(node); - var target = node.querySelector('#target'); - var virtualTarget = axe.utils.getNodeFromTree(target); - var result = checkEvaluate.apply(checkContext, [ - target, - {}, - virtualTarget - ]); - assert.isFalse(result); - } - ); + it('should return false in a shadow DOM fail', () => { + const node = document.createElement('div'); + node.innerHTML = '
                    7. My list item
                    8. '; + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = '
                      '; + fixtureSetup(node); + const target = node.querySelector('#target'); + const virtualTarget = axe.utils.getNodeFromTree(target); + const result = checkEvaluate.apply(checkContext, [ + target, + {}, + virtualTarget + ]); + assert.isFalse(result); + }); }); diff --git a/test/checks/lists/structured-dlitems.js b/test/checks/lists/structured-dlitems.js index 4060fb8dc..285552491 100644 --- a/test/checks/lists/structured-dlitems.js +++ b/test/checks/lists/structured-dlitems.js @@ -1,16 +1,13 @@ -describe('structured-dlitems', function () { - 'use strict'; +describe('structured-dlitems', () => { + const fixture = document.getElementById('fixture'); + const checkSetup = axe.testUtils.checkSetup; - var fixture = document.getElementById('fixture'); - var checkSetup = axe.testUtils.checkSetup; - var shadowSupport = axe.testUtils.shadowSupport; - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should return false if the list has no contents', function () { - var checkArgs = checkSetup('
                      '); + it('should return false if the list has no contents', () => { + const checkArgs = checkSetup('
                      '); assert.isFalse( axe.testUtils .getCheckEvaluate('structured-dlitems') @@ -18,8 +15,8 @@ describe('structured-dlitems', function () { ); }); - it('should return true if the list has only a dd', function () { - var checkArgs = checkSetup('
                      A list
                      '); + it('should return true if the list has only a dd', () => { + const checkArgs = checkSetup('
                      A list
                      '); assert.isTrue( axe.testUtils .getCheckEvaluate('structured-dlitems') @@ -27,8 +24,8 @@ describe('structured-dlitems', function () { ); }); - it('should return true if the list has only a dt', function () { - var checkArgs = checkSetup('
                      A list
                      '); + it('should return true if the list has only a dt', () => { + const checkArgs = checkSetup('
                      A list
                      '); assert.isTrue( axe.testUtils @@ -37,8 +34,8 @@ describe('structured-dlitems', function () { ); }); - it('should return true if the list has dt and dd in the incorrect order', function () { - var checkArgs = checkSetup( + it('should return true if the list has dt and dd in the incorrect order', () => { + const checkArgs = checkSetup( '
                      A list
                      An item
                      ' ); @@ -49,8 +46,8 @@ describe('structured-dlitems', function () { ); }); - it('should return true if the list has dt and dd in the correct order as non-child descendants', function () { - var checkArgs = checkSetup( + it('should return true if the list has dt and dd in the correct order as non-child descendants', () => { + const checkArgs = checkSetup( '
                      An item
                      A list
                      ' ); @@ -61,8 +58,8 @@ describe('structured-dlitems', function () { ); }); - it('should return false if the list has dt and dd in the correct order', function () { - var checkArgs = checkSetup( + it('should return false if the list has dt and dd in the correct order', () => { + const checkArgs = checkSetup( '
                      An item
                      A list
                      ' ); @@ -73,8 +70,8 @@ describe('structured-dlitems', function () { ); }); - it('should return false if the list has a correctly-ordered dt and dd with other content', function () { - var checkArgs = checkSetup( + it('should return false if the list has a correctly-ordered dt and dd with other content', () => { + const checkArgs = checkSetup( '
                      Stuff
                      Item one
                      Description

                      Not a list

                      ' ); @@ -85,37 +82,31 @@ describe('structured-dlitems', function () { ); }); - (shadowSupport.v1 ? it : xit)( - 'should return false in a shadow DOM pass', - function () { - var node = document.createElement('div'); - node.innerHTML = '
                      Grayhound bus
                      at dawn
                      '; - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = '
                      '; - - var checkArgs = checkSetup(node, 'dl'); - assert.isFalse( - axe.testUtils - .getCheckEvaluate('structured-dlitems') - .apply(null, checkArgs) - ); - } - ); - - (shadowSupport.v1 ? it : xit)( - 'should return true in a shadow DOM fail', - function () { - var node = document.createElement('div'); - node.innerHTML = '
                      Galileo
                      Figaro
                      '; - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = '
                      '; - - var checkArgs = checkSetup(node, 'dl'); - assert.isTrue( - axe.testUtils - .getCheckEvaluate('structured-dlitems') - .apply(null, checkArgs) - ); - } - ); + it('should return false in a shadow DOM pass', () => { + const node = document.createElement('div'); + node.innerHTML = '
                      Grayhound bus
                      at dawn
                      '; + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = '
                      '; + + const checkArgs = checkSetup(node, 'dl'); + assert.isFalse( + axe.testUtils + .getCheckEvaluate('structured-dlitems') + .apply(null, checkArgs) + ); + }); + + it('should return true in a shadow DOM fail', () => { + const node = document.createElement('div'); + node.innerHTML = '
                      Galileo
                      Figaro
                      '; + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = '
                      '; + + const checkArgs = checkSetup(node, 'dl'); + assert.isTrue( + axe.testUtils + .getCheckEvaluate('structured-dlitems') + .apply(null, checkArgs) + ); + }); }); diff --git a/test/checks/media/caption.js b/test/checks/media/caption.js index 88fce5310..8fe5baf34 100644 --- a/test/checks/media/caption.js +++ b/test/checks/media/caption.js @@ -1,42 +1,39 @@ -describe('caption', function () { - 'use strict'; +describe('caption', () => { + const fixture = document.getElementById('fixture'); + const checkSetup = axe.testUtils.checkSetup; - var fixture = document.getElementById('fixture'); - var shadowSupport = axe.testUtils.shadowSupport; - var checkSetup = axe.testUtils.checkSetup; - - afterEach(function () { + afterEach(() => { fixture.innerHTML = ''; }); - it('should return undefined if there is no track element', function () { - var checkArgs = checkSetup('', 'audio'); + it('should return undefined if there is no track element', () => { + const checkArgs = checkSetup('', 'audio'); assert.isUndefined(checks.caption.evaluate.apply(null, checkArgs)); }); - it('should return undefined if there is no kind=captions attribute', function () { - var checkArgs = checkSetup( + it('should return undefined if there is no kind=captions attribute', () => { + const checkArgs = checkSetup( '', 'audio' ); assert.isUndefined(checks.caption.evaluate.apply(null, checkArgs)); }); - it('should pass if there is a kind=captions attribute', function () { - var checkArgs = checkSetup('', 'audio'); + it('should pass if there is a kind=captions attribute', () => { + const checkArgs = checkSetup( + '', + 'audio' + ); assert.isFalse(checks.caption.evaluate.apply(null, checkArgs)); }); - (shadowSupport.v1 ? it : xit)( - 'should get track from composed tree', - function () { - var node = document.createElement('div'); - node.innerHTML = ''; - var shadow = node.attachShadow({ mode: 'open' }); - shadow.innerHTML = ''; + it('should get track from composed tree', () => { + const node = document.createElement('div'); + node.innerHTML = ''; + const shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = ''; - var checkArgs = checkSetup(node, {}, 'audio'); - assert.isFalse(checks.caption.evaluate.apply(null, checkArgs)); - } - ); + const checkArgs = checkSetup(node, {}, 'audio'); + assert.isFalse(checks.caption.evaluate.apply(null, checkArgs)); + }); }); diff --git a/test/checks/media/frame-tested.js b/test/checks/media/frame-tested.js index 9addbfdcc..986c7223c 100644 --- a/test/checks/media/frame-tested.js +++ b/test/checks/media/frame-tested.js @@ -1,22 +1,20 @@ -describe('frame-tested', function () { - 'use strict'; +describe('frame-tested', () => { + const checkEvaluate = axe.testUtils.getCheckEvaluate('frame-tested'); + const frameTestedAfter = checks['frame-tested'].after; - var checkEvaluate = axe.testUtils.getCheckEvaluate('frame-tested'); - var frameTestedAfter = checks['frame-tested'].after; - - describe('evaluate', function () { - it('returns undefined', function () { + describe('evaluate', () => { + it('returns undefined', () => { assert.isUndefined(checkEvaluate()); }); - it('returns false if passed isViolation:true', function () { + it('returns false if passed isViolation:true', () => { assert.isFalse(checkEvaluate(null, { isViolation: true })); }); }); - describe('after', function () { - it('changes result to true if frame has been tested', function () { - var results = [ + describe('after', () => { + it('changes result to true if frame has been tested', () => { + const results = [ { result: undefined, node: { @@ -49,7 +47,7 @@ describe('frame-tested', function () { } ]; - var afterResults = frameTestedAfter(results); + const afterResults = frameTestedAfter(results); assert.lengthOf(afterResults, 2); assert.isTrue(afterResults[0].result); @@ -63,8 +61,8 @@ describe('frame-tested', function () { ]); }); - it('does not change result when iframe has not been tested', function () { - var results = [ + it('does not change result when iframe has not been tested', () => { + const results = [ { result: undefined, node: { @@ -97,7 +95,7 @@ describe('frame-tested', function () { } ]; - var afterResults = frameTestedAfter(results); + const afterResults = frameTestedAfter(results); assert.lengthOf(afterResults, 3); assert.isTrue(afterResults[0].result); @@ -116,8 +114,8 @@ describe('frame-tested', function () { ]); }); - it('works with shadow DOM', function () { - var results = [ + it('works with shadow DOM', () => { + const results = [ { result: undefined, node: { @@ -144,7 +142,7 @@ describe('frame-tested', function () { } ]; - var afterResults = frameTestedAfter(results); + const afterResults = frameTestedAfter(results); assert.lengthOf(afterResults, 2); assert.isTrue(afterResults[0].result); @@ -158,8 +156,8 @@ describe('frame-tested', function () { ]); }); - it('works with nested shadow DOM and iframes', function () { - var results = [ + it('works with nested shadow DOM and iframes', () => { + const results = [ { result: undefined, node: { @@ -220,7 +218,7 @@ describe('frame-tested', function () { } ]; - var afterResults = frameTestedAfter(results); + const afterResults = frameTestedAfter(results); assert.lengthOf(afterResults, 4); assert.isTrue(afterResults[0].result); diff --git a/test/checks/media/no-autoplay-audio.js b/test/checks/media/no-autoplay-audio.js index d0b24475c..251ebdf37 100644 --- a/test/checks/media/no-autoplay-audio.js +++ b/test/checks/media/no-autoplay-audio.js @@ -1,4 +1,5 @@ describe('no-autoplay-audio', () => { + const html = axe.testUtils.html; const check = checks['no-autoplay-audio']; const checkSetup = axe.testUtils.checkSetup; const checkContext = axe.testUtils.MockCheckContext(); @@ -29,7 +30,7 @@ describe('no-autoplay-audio', () => { }); it('returns false when