diff --git a/.changeset/seven-chefs-confess.md b/.changeset/seven-chefs-confess.md new file mode 100644 index 00000000000..dd4ef94fc0b --- /dev/null +++ b/.changeset/seven-chefs-confess.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +Update Radio to only use disabled when provided and no longer set aria-disabled diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-dark-colorblind-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-dark-colorblind-linux.png new file mode 100644 index 00000000000..55f4b48fe96 Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-dark-dimmed-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-dark-dimmed-linux.png new file mode 100644 index 00000000000..717cb755be9 Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-dark-high-contrast-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-dark-high-contrast-linux.png new file mode 100644 index 00000000000..704e21195cf Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-dark-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-dark-linux.png new file mode 100644 index 00000000000..55f4b48fe96 Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-dark-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-dark-tritanopia-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-dark-tritanopia-linux.png new file mode 100644 index 00000000000..55f4b48fe96 Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-dark-colorblind-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-dark-colorblind-linux.png new file mode 100644 index 00000000000..a7a748d18ca Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-dark-dimmed-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-dark-dimmed-linux.png new file mode 100644 index 00000000000..86bcc30276e Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-dark-high-contrast-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-dark-high-contrast-linux.png new file mode 100644 index 00000000000..6c119e027f2 Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-dark-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-dark-linux.png new file mode 100644 index 00000000000..a7a748d18ca Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-dark-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-dark-tritanopia-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-dark-tritanopia-linux.png new file mode 100644 index 00000000000..a7a748d18ca Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-light-colorblind-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-light-colorblind-linux.png new file mode 100644 index 00000000000..3de9aaeaebe Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-light-high-contrast-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-light-high-contrast-linux.png new file mode 100644 index 00000000000..3bd658da926 Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-light-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-light-linux.png new file mode 100644 index 00000000000..3de9aaeaebe Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-light-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-light-tritanopia-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-light-tritanopia-linux.png new file mode 100644 index 00000000000..3de9aaeaebe Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-disabled-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-light-colorblind-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-light-colorblind-linux.png new file mode 100644 index 00000000000..8ca7a32b0a3 Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-light-high-contrast-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-light-high-contrast-linux.png new file mode 100644 index 00000000000..f68e8b17780 Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-light-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-light-linux.png new file mode 100644 index 00000000000..8ca7a32b0a3 Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-light-linux.png differ diff --git a/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-light-tritanopia-linux.png b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-light-tritanopia-linux.png new file mode 100644 index 00000000000..8ca7a32b0a3 Binary files /dev/null and b/.playwright/snapshots/components/RadioGroup.test.ts-snapshots/RadioGroup-Default-light-tritanopia-linux.png differ diff --git a/e2e/components/RadioGroup.test.ts b/e2e/components/RadioGroup.test.ts new file mode 100644 index 00000000000..06bd2d9635e --- /dev/null +++ b/e2e/components/RadioGroup.test.ts @@ -0,0 +1,81 @@ +import {test, expect} from '@playwright/test' +import {visit} from '../test-helpers/storybook' +import {themes} from '../test-helpers/themes' + +test.describe('RadioGroup', () => { + test.describe('Default', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-forms-radiogroup-examples--default', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot()).toMatchSnapshot(`RadioGroup.Default.${theme}.png`) + }) + + test('disabled @vrt', async ({page}) => { + await visit(page, { + id: 'components-forms-radiogroup-examples--default', + globals: { + colorScheme: theme, + }, + args: { + disabled: true, + }, + }) + + expect(await page.screenshot()).toMatchSnapshot(`RadioGroup.Default.disabled.${theme}.png`) + + await expect(page).toHaveNoViolations({ + rules: { + 'color-contrast': { + enabled: theme !== 'dark_dimmed', + }, + }, + }) + }) + + test('disabled @aat', async ({page}) => { + await visit(page, { + id: 'components-forms-radiogroup-examples--default', + globals: { + colorScheme: theme, + }, + args: { + disabled: true, + }, + }) + + await expect(page).toHaveNoViolations({ + rules: { + 'color-contrast': { + enabled: theme !== 'dark_dimmed', + }, + }, + }) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-forms-radiogroup-examples--default', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations({ + rules: { + 'color-contrast': { + enabled: theme !== 'dark_dimmed', + }, + }, + }) + }) + }) + } + }) +}) diff --git a/e2e/matchers/toHaveNoViolations.ts b/e2e/matchers/toHaveNoViolations.ts index 396b10ffa7b..e4d48c6cfef 100644 --- a/e2e/matchers/toHaveNoViolations.ts +++ b/e2e/matchers/toHaveNoViolations.ts @@ -1,6 +1,5 @@ import {Page, expect, test} from '@playwright/test' -import AxeBuilder from '@axe-core/playwright' -import {AxeResults} from 'axe-core' +import {AxeResults, source} from 'axe-core' import path from 'node:path' import fs from 'node:fs' @@ -21,23 +20,62 @@ const defaultOptions = { region: { enabled: false, }, + // Custom rules + 'avoid-both-disabled-and-aria-disabled': { + enabled: true, + }, }, } expect.extend({ async toHaveNoViolations(page: Page, options = {rules: {}}) { - // @ts-ignore Page from @playwright/test should satisfy Page from - // playwright-core - const result = await new AxeBuilder({page}) - .options({ - ...defaultOptions, - ...options, - rules: { - ...defaultOptions.rules, - ...options.rules, - }, + const runConfig = { + ...defaultOptions, + ...options, + rules: { + ...defaultOptions.rules, + ...options.rules, + }, + } + + await page.evaluate(source) + + const result: AxeResults = await page.evaluate(runConfig => { + // @ts-ignore `axe` is a global variable defined by page.evaluate() above + const axe = window.axe + + axe.configure({ + rules: [ + { + id: 'avoid-both-disabled-and-aria-disabled', + excludeHidden: true, + selector: 'button, fieldset, input, optgroup, option, select, textarea', + all: ['check-avoid-both-disabled-and-aria-disabled'], + any: [], + metadata: { + help: '[aria-disabled] may be used in place of native HTML [disabled] to allow tab-focus on an otherwise ignored element. Setting both attributes is contradictory.', + helpUrl: 'https://www.w3.org/TR/html-aria/#docconformance-attr', + }, + tags: ['custom-github-rule'], + }, + ], + checks: [ + { + id: 'check-avoid-both-disabled-and-aria-disabled', + /** + * Check an element with native `disabled` support doesn't have both `disabled` and `aria-disabled` set. + */ + evaluate: (el: Element) => !(el.hasAttribute('aria-disabled') && el.hasAttribute('disabled')), + metadata: { + impact: 'critical', + }, + }, + ], }) - .analyze() + + // @ts-ignore `axe` is a global variable defined by page.evaluate() above + return axe.run(runConfig) + }, runConfig) saveResult(result) diff --git a/e2e/test-helpers/storybook.ts b/e2e/test-helpers/storybook.ts index bc9e5459591..396ae37b2f0 100644 --- a/e2e/test-helpers/storybook.ts +++ b/e2e/test-helpers/storybook.ts @@ -3,19 +3,27 @@ import {waitForImages} from './waitForImages' interface Options { id: string + args?: Record globals?: Record } const {STORYBOOK_URL = 'http://localhost:6006'} = process.env export async function visit(page: Page, options: Options) { - const {id, globals} = options + const {id, args, globals} = options // In CI, the static server strips `.html` extensions const url = process.env.CI ? new URL(`${STORYBOOK_URL}/iframe`) : new URL(`${STORYBOOK_URL}/iframe.html`) url.searchParams.set('id', id) url.searchParams.set('viewMode', 'story') + if (args) { + const serialized = Object.entries(args) + .map(([key, value]) => `${key}:${value}`) + .join(',') + url.searchParams.set('args', serialized) + } + if (globals) { let params = '' for (const [key, value] of Object.entries(globals)) { diff --git a/package-lock.json b/package-lock.json index d3047445568..28603141614 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,6 @@ "styled-system": "^5.1.5" }, "devDependencies": { - "@axe-core/playwright": "4.5.0", "@babel/cli": "7.19.3", "@babel/core": "7.14.8", "@babel/eslint-parser": "7.15.7", @@ -182,18 +181,6 @@ "node": ">=6.0.0" } }, - "node_modules/@axe-core/playwright": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.5.0.tgz", - "integrity": "sha512-YBkSBuxeSXb9FRUZU7NsZZFU/sTluZt71YqTRLrS/1eycZqIYMIALo9D/s17zPoBsFwJRKPkKvQy+dmDHHy4yQ==", - "dev": true, - "dependencies": { - "axe-core": "^4.5.0" - }, - "peerDependencies": { - "playwright": ">= 1.0.0" - } - }, "node_modules/@babel/cli": { "version": "7.19.3", "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.19.3.tgz", @@ -43472,15 +43459,6 @@ } } }, - "@axe-core/playwright": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.5.0.tgz", - "integrity": "sha512-YBkSBuxeSXb9FRUZU7NsZZFU/sTluZt71YqTRLrS/1eycZqIYMIALo9D/s17zPoBsFwJRKPkKvQy+dmDHHy4yQ==", - "dev": true, - "requires": { - "axe-core": "^4.5.0" - } - }, "@babel/cli": { "version": "7.19.3", "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.19.3.tgz", diff --git a/package.json b/package.json index 75397792620..a7dc76385f8 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,6 @@ "styled-system": "^5.1.5" }, "devDependencies": { - "@axe-core/playwright": "4.5.0", "@babel/cli": "7.19.3", "@babel/core": "7.14.8", "@babel/eslint-parser": "7.15.7", diff --git a/script/generate-e2e-tests.js b/script/generate-e2e-tests.js index 6f2776f1e8f..602e2fb3c0d 100644 --- a/script/generate-e2e-tests.js +++ b/script/generate-e2e-tests.js @@ -228,6 +228,17 @@ const components = new Map([ ], }, ], + [ + 'RadioGroup', + { + stories: [ + { + id: 'components-forms-radiogroup-examples--default', + name: 'Default', + }, + ], + }, + ], [ 'UnderlineNav', { diff --git a/src/Radio.tsx b/src/Radio.tsx index 0e81432f7b0..ce2024773e4 100644 --- a/src/Radio.tsx +++ b/src/Radio.tsx @@ -76,7 +76,6 @@ const Radio = React.forwardRef( name={name} ref={ref} disabled={disabled} - aria-disabled={disabled ? 'true' : 'false'} checked={checked} aria-checked={checked ? 'true' : 'false'} required={required} diff --git a/src/__tests__/Radio.test.tsx b/src/__tests__/Radio.test.tsx index 18f98c44eba..63e3e7ddc5f 100644 --- a/src/__tests__/Radio.test.tsx +++ b/src/__tests__/Radio.test.tsx @@ -104,18 +104,14 @@ describe('Radio', () => { expect(radio.disabled).toEqual(true) expect(radio).not.toBeChecked() - expect(radio).toHaveAttribute('aria-disabled', 'true') fireEvent.change(radio) expect(radio.disabled).toEqual(true) expect(radio).not.toBeChecked() - expect(radio).toHaveAttribute('aria-disabled', 'true') // remove disabled attribute and retest rerender() - - expect(radio).toHaveAttribute('aria-disabled', 'false') }) it('renders an uncontrolled component correctly', () => { diff --git a/src/__tests__/__snapshots__/CheckboxOrRadioGroup.test.tsx.snap b/src/__tests__/__snapshots__/CheckboxOrRadioGroup.test.tsx.snap index 035bafc69c7..9ea188ed38e 100644 --- a/src/__tests__/__snapshots__/CheckboxOrRadioGroup.test.tsx.snap +++ b/src/__tests__/__snapshots__/CheckboxOrRadioGroup.test.tsx.snap @@ -114,7 +114,6 @@ exports[`CheckboxOrRadioGroup renders consistently 1`] = ` >