From 8bc2c2faa2ec7ba59499a7836a55a23214b6e770 Mon Sep 17 00:00:00 2001 From: Wagner Maciel Date: Thu, 15 May 2025 17:03:34 -0400 Subject: [PATCH 1/2] refactor(cdk/testing): move runAccessibilityChecks --- src/cdk-experimental/radio/BUILD.bazel | 1 - src/cdk-experimental/radio/radio.spec.ts | 75 +---------------- src/cdk/testing/private/BUILD.bazel | 1 + src/cdk/testing/private/public-api.ts | 1 + .../private/run-accessibility-checks.ts | 84 +++++++++++++++++++ 5 files changed, 87 insertions(+), 75 deletions(-) create mode 100644 src/cdk/testing/private/run-accessibility-checks.ts diff --git a/src/cdk-experimental/radio/BUILD.bazel b/src/cdk-experimental/radio/BUILD.bazel index 3838af15170b..bfe762afaa7e 100644 --- a/src/cdk-experimental/radio/BUILD.bazel +++ b/src/cdk-experimental/radio/BUILD.bazel @@ -27,7 +27,6 @@ ts_project( ":radio", "//:node_modules/@angular/core", "//:node_modules/@angular/platform-browser", - "//:node_modules/axe-core", "//src/cdk/testing/private", ], ) diff --git a/src/cdk-experimental/radio/radio.spec.ts b/src/cdk-experimental/radio/radio.spec.ts index 249ca625575d..17eddb854555 100644 --- a/src/cdk-experimental/radio/radio.spec.ts +++ b/src/cdk-experimental/radio/radio.spec.ts @@ -3,80 +3,7 @@ import {CdkRadioButton, CdkRadioGroup} from './radio'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {BidiModule, Direction} from '@angular/cdk/bidi'; -import {provideFakeDirectionality} from '@angular/cdk/testing/private'; -import axe from 'axe-core'; - -// Basic ANSI color functions because chalk has issues with unit tests. -const colors = { - red: (text: string) => `\x1b[31m${text}\x1b[0m`, - yellow: (text: string) => `\x1b[33m${text}\x1b[0m`, - blue: (text: string) => `\x1b[34m${text}\x1b[0m`, - magenta: (text: string) => `\x1b[35m${text}\x1b[0m`, - cyan: (text: string) => `\x1b[36m${text}\x1b[0m`, - gray: (text: string) => `\x1b[90m${text}\x1b[0m`, - underline: (text: string) => `\x1b[4m${text}\x1b[0m`, - default: (text: string) => `\x1b[0m${text}\x1b[0m`, -}; - -// TODO: Move this to a separate folder/file so it can be reused across components. -async function runAccessibilityChecks(root: HTMLElement): Promise { - const results = await axe.run(root); - - if (!results.violations.length) { - return; - } - - const reportLines: string[] = []; - const append = (text: string) => reportLines.push(colors.default(text)); - append(colors.red(`Found ${results.violations.length} accessibility violation(s):`)); - - results.violations.forEach((violation, index) => { - append(''); - append(colors.red(`Violation ${index + 1}: ${violation.id}\n`)); - - let impactText = violation.impact || 'unknown'; - switch (violation.impact) { - case 'critical': - impactText = colors.red(impactText); - break; - case 'serious': - impactText = colors.yellow(impactText); - break; - case 'moderate': - impactText = colors.blue(impactText); - break; - case 'minor': - impactText = colors.gray(impactText); - break; - default: - impactText = colors.default(impactText); - break; - } - - append(` Impact: ${impactText}`); - append(` Description: ${violation.description}`); - append(` Help: ${violation.help}`); - append(` Help URL: ${colors.underline(colors.blue(violation.helpUrl))}\n`); - - if (violation.nodes && violation.nodes.length > 0) { - append(' Failing Elements:'); - violation.nodes.forEach((node, nodeIndex) => { - append(colors.cyan(` Node ${nodeIndex + 1}:`)); - if (node.target && node.target.length > 0) { - append(` Selector: ${colors.magenta(node.target.join(', '))}`); - } - if (node.failureSummary) { - append(' Failure Summary:'); - node.failureSummary - .split('\n') - .forEach(line => append(colors.yellow(` ${line.trim()}`))); - } - }); - } - }); - - fail(reportLines.join('\n')); -} +import {provideFakeDirectionality, runAccessibilityChecks} from '@angular/cdk/testing/private'; describe('CdkRadioGroup', () => { let fixture: ComponentFixture; diff --git a/src/cdk/testing/private/BUILD.bazel b/src/cdk/testing/private/BUILD.bazel index 195295d4f470..9af9daa568f3 100644 --- a/src/cdk/testing/private/BUILD.bazel +++ b/src/cdk/testing/private/BUILD.bazel @@ -12,6 +12,7 @@ ts_project( deps = [ "//:node_modules/@angular/core", "//:node_modules/@types/jasmine", + "//:node_modules/axe-core", "//src/cdk/bidi", "//src/cdk/testing/testbed", ], diff --git a/src/cdk/testing/private/public-api.ts b/src/cdk/testing/private/public-api.ts index 5b973a376263..f69ee03e8d42 100644 --- a/src/cdk/testing/private/public-api.ts +++ b/src/cdk/testing/private/public-api.ts @@ -9,6 +9,7 @@ export * from './fake-directionality'; export * from './text-dedent'; export * from './wrapped-error-message'; +export * from './run-accessibility-checks'; // Re-exported for convenience. export * from '../testbed/fake-events'; diff --git a/src/cdk/testing/private/run-accessibility-checks.ts b/src/cdk/testing/private/run-accessibility-checks.ts new file mode 100644 index 000000000000..b02b966f7a95 --- /dev/null +++ b/src/cdk/testing/private/run-accessibility-checks.ts @@ -0,0 +1,84 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import axe from 'axe-core'; + +// Basic ANSI color functions because chalk has issues with unit tests. +const colors = { + red: (text: string) => `\x1b[31m${text}\x1b[0m`, + yellow: (text: string) => `\x1b[33m${text}\x1b[0m`, + blue: (text: string) => `\x1b[34m${text}\x1b[0m`, + magenta: (text: string) => `\x1b[35m${text}\x1b[0m`, + cyan: (text: string) => `\x1b[36m${text}\x1b[0m`, + gray: (text: string) => `\x1b[90m${text}\x1b[0m`, + underline: (text: string) => `\x1b[4m${text}\x1b[0m`, + default: (text: string) => `\x1b[0m${text}\x1b[0m`, +}; + +/** + * Runs accessibility checks on a specified HTML element using the axe-core library. + * @param root The root HTML element to check for accessibility violations. + */ +export async function runAccessibilityChecks(root: HTMLElement): Promise { + const results = await axe.run(root); + + if (!results.violations.length) { + return; + } + + const reportLines: string[] = []; + const append = (text: string) => reportLines.push(colors.default(text)); + append(colors.red(`Found ${results.violations.length} accessibility violation(s):`)); + + results.violations.forEach((violation, index) => { + append(''); + append(colors.red(`Violation ${index + 1}: ${violation.id}\n`)); + + let impactText = violation.impact || 'unknown'; + switch (violation.impact) { + case 'critical': + impactText = colors.red(impactText); + break; + case 'serious': + impactText = colors.yellow(impactText); + break; + case 'moderate': + impactText = colors.blue(impactText); + break; + case 'minor': + impactText = colors.gray(impactText); + break; + default: + impactText = colors.default(impactText); + break; + } + + append(` Impact: ${impactText}`); + append(` Description: ${violation.description}`); + append(` Help: ${violation.help}`); + append(` Help URL: ${colors.underline(colors.blue(violation.helpUrl))}\n`); + + if (violation.nodes && violation.nodes.length > 0) { + append(' Failing Elements:'); + violation.nodes.forEach((node, nodeIndex) => { + append(colors.cyan(` Node ${nodeIndex + 1}:`)); + if (node.target && node.target.length > 0) { + append(` Selector: ${colors.magenta(node.target.join(', '))}`); + } + if (node.failureSummary) { + append(' Failure Summary:'); + node.failureSummary + .split('\n') + .forEach(line => append(colors.yellow(` ${line.trim()}`))); + } + }); + } + }); + + fail(reportLines.join('\n')); +} From 5091d0a2435a47d581f687c839ef870eba6e1cb2 Mon Sep 17 00:00:00 2001 From: Wagner Maciel Date: Thu, 15 May 2025 17:09:29 -0400 Subject: [PATCH 2/2] fixup! refactor(cdk/testing): move runAccessibilityChecks --- pkg-externals.bzl | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg-externals.bzl b/pkg-externals.bzl index e07031a49086..c95009e34d54 100644 --- a/pkg-externals.bzl +++ b/pkg-externals.bzl @@ -39,6 +39,7 @@ PKG_EXTERNALS = [ "@angular/youtube-player", # Third-party libraries. + "axe-core", "kagekiri", "moment", "moment/locale/fr",