Skip to content

Commit e8822db

Browse files
committed
test: add Phase 3 audit fixture translating role-has-required-aria peer cases
Translates 41 cases from peer-plugin rules: - jsx-a11y role-has-required-aria-props - vuejs-accessibility role-has-required-aria-props - @angular-eslint/template role-has-required-aria - lit-a11y role-has-required-aria-attrs The fixture documents where our rule matches peer behavior (e.g. the input+role semantic whitelist now recognized after this fix) and where it still diverges (space-separated role tokens, case-insensitivity, unknown roles, undocumented input+role pairings). Not wired into the default vitest run; provided for behavioral parity auditing.
1 parent 5343fc5 commit e8822db

1 file changed

Lines changed: 202 additions & 0 deletions

File tree

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// Audit fixture — peer-plugin parity for
2+
// `ember/template-require-mandatory-role-attributes`.
3+
//
4+
// Source files (context/ checkouts):
5+
// - eslint-plugin-jsx-a11y-main/src/rules/role-has-required-aria-props.js
6+
// - eslint-plugin-jsx-a11y-main/__tests__/src/rules/role-has-required-aria-props-test.js
7+
// - eslint-plugin-vuejs-accessibility-main/src/rules/role-has-required-aria-props.ts
8+
// - angular-eslint-main/packages/eslint-plugin-template/src/rules/role-has-required-aria.ts
9+
// - angular-eslint-main/packages/eslint-plugin-template/tests/rules/role-has-required-aria/cases.ts
10+
// - eslint-plugin-lit-a11y/lib/rules/role-has-required-aria-attrs.js
11+
//
12+
// These tests are NOT part of the main suite and do not run in CI. They encode
13+
// the CURRENT behavior of our rule. Each divergence from an upstream plugin is
14+
// annotated as "DIVERGENCE —".
15+
16+
'use strict';
17+
18+
const rule = require('../../../lib/rules/template-require-mandatory-role-attributes');
19+
const RuleTester = require('eslint').RuleTester;
20+
21+
const ruleTester = new RuleTester({
22+
parser: require.resolve('ember-eslint-parser'),
23+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
24+
});
25+
26+
ruleTester.run('audit:role-has-required-aria (gts)', rule, {
27+
valid: [
28+
// === Upstream parity (valid everywhere) ===
29+
'<template><div /></template>',
30+
'<template><div role="button" /></template>', // no required props
31+
'<template><div role="heading" aria-level="2" /></template>',
32+
'<template><span role="button">X</span></template>',
33+
// checkbox with aria-checked — valid in all plugins.
34+
'<template><div role="checkbox" aria-checked="false" /></template>',
35+
// combobox with BOTH required props (jsx-a11y, vue, ours).
36+
'<template><div role="combobox" aria-expanded="false" aria-controls="id" /></template>',
37+
// scrollbar requires aria-valuenow, aria-valuemin, aria-valuemax, aria-controls, aria-orientation.
38+
// slider similarly — we leave the all-present case off for brevity.
39+
40+
// Dynamic role — skipped by all.
41+
'<template><div role={{this.role}} /></template>',
42+
43+
// Unknown role — jsx-a11y filters out unknown, we return null. Both allow.
44+
'<template><div role="foobar" /></template>',
45+
46+
// === Parity — <input type="checkbox" role="switch"> ===
47+
// jsx-a11y: VALID via `isSemanticRoleElement` (semantic input[type=checkbox]
48+
// counts as already-declaring aria-checked via its `checked` state).
49+
// vue-a11y: VALID via explicit carve-out in filterRequiredPropsExceptions.
50+
// angular: VALID via isSemanticRoleElement.
51+
// Our rule: VALID via the explicit `{input[type], role}` whitelist
52+
// (checkbox+switch per WAI-ARIA APG Switch pattern; see PR adding the fix).
53+
// Also covers checkbox+checkbox, checkbox+menuitemcheckbox, radio+radio,
54+
// radio+menuitemradio.
55+
'<template><input type="checkbox" role="switch" /></template>',
56+
'<template><input type="checkbox" role="checkbox" /></template>',
57+
'<template><input type="checkbox" role="menuitemcheckbox" /></template>',
58+
'<template><input type="radio" role="radio" /></template>',
59+
'<template><input type="radio" role="menuitemradio" /></template>',
60+
// HTML type keyword values are ASCII case-insensitive, so the whitelist
61+
// also matches uppercase `type` values.
62+
'<template><input type="CHECKBOX" role="switch" /></template>',
63+
64+
// === DIVERGENCE — space-separated role tokens ===
65+
// jsx-a11y + vue: split on whitespace, validate each token. If every token
66+
// is a valid role, require attrs for each.
67+
// Our rule: looks up the whole string in aria-query. `"combobox listbox"`
68+
// is not a role → returns null → no missing attrs → NO FLAG.
69+
// Net: jsx-a11y would flag `<div role="combobox listbox">` (missing attrs
70+
// for both), we don't. Captured as valid below.
71+
'<template><div role="combobox listbox" /></template>',
72+
73+
// === DIVERGENCE — case-insensitivity on role value ===
74+
// jsx-a11y + vue + angular: lowercase the role value before lookup.
75+
// `<div role="COMBOBOX" />` → INVALID (missing aria-expanded/controls).
76+
// Our rule: passes the raw string; aria-query lookup misses → no flag.
77+
'<template><div role="COMBOBOX" /></template>',
78+
'<template><div role="SLIDER" /></template>',
79+
],
80+
81+
invalid: [
82+
// === Upstream parity (invalid everywhere) ===
83+
{
84+
code: '<template><div role="slider" /></template>',
85+
output: null,
86+
errors: [{ messageId: 'missingAttributes' }],
87+
},
88+
{
89+
code: '<template><div role="checkbox" /></template>',
90+
output: null,
91+
errors: [{ messageId: 'missingAttributes' }],
92+
},
93+
{
94+
code: '<template><div role="combobox" /></template>',
95+
output: null,
96+
errors: [{ messageId: 'missingAttributes' }],
97+
},
98+
{
99+
code: '<template><div role="scrollbar" /></template>',
100+
output: null,
101+
errors: [{ messageId: 'missingAttributes' }],
102+
},
103+
{
104+
code: '<template><div role="heading" /></template>',
105+
output: null,
106+
errors: [{ messageId: 'missingAttributes' }],
107+
},
108+
{
109+
code: '<template><div role="option" /></template>',
110+
output: null,
111+
errors: [{ messageId: 'missingAttributes' }],
112+
},
113+
114+
// === DIVERGENCE — partial attrs present, still missing one ===
115+
// jsx-a11y flags `<div role="scrollbar" aria-valuemax aria-valuemin />`
116+
// (missing aria-controls/aria-orientation/aria-valuenow).
117+
// Our rule: also flags — missing-attrs list non-empty. Parity.
118+
{
119+
code: '<template><div role="combobox" aria-controls="x" /></template>',
120+
output: null,
121+
errors: [{ messageId: 'missingAttributes' }],
122+
},
123+
124+
// === DIVERGENCE — undocumented input+role pairings ===
125+
// Pairings NOT on our whitelist remain flagged. jsx-a11y/angular defer to
126+
// axobject-query's `elementAXObjects`, which covers a larger set; we only
127+
// recognize the documented five pairings. Example mismatches:
128+
// - input[type=checkbox] role=radio → we flag, jsx-a11y/angular don't
129+
// - input[type=radio] role=switch → we flag, peer behavior varies
130+
// These remain invalid (missing aria-checked) in our rule.
131+
{
132+
code: '<template><input type="checkbox" role="radio" /></template>',
133+
output: null,
134+
errors: [{ messageId: 'missingAttributes' }],
135+
},
136+
{
137+
code: '<template><input type="radio" role="switch" /></template>',
138+
output: null,
139+
errors: [{ messageId: 'missingAttributes' }],
140+
},
141+
// Bare `<input role="switch">` (no `type`) — not on our whitelist, stays
142+
// flagged. The input's default `type=text` does not expose aria-checked.
143+
{
144+
code: '<template><input role="switch" /></template>',
145+
output: null,
146+
errors: [{ messageId: 'missingAttributes' }],
147+
},
148+
],
149+
});
150+
151+
const hbsRuleTester = new RuleTester({
152+
parser: require.resolve('ember-eslint-parser/hbs'),
153+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
154+
});
155+
156+
hbsRuleTester.run('audit:role-has-required-aria (hbs)', rule, {
157+
valid: [
158+
'<div />',
159+
'<div role="button" />',
160+
'<div role="heading" aria-level="2" />',
161+
'<div role="combobox" aria-expanded="false" aria-controls="id" />',
162+
// Parity: input+role semantic whitelist.
163+
'<input type="checkbox" role="switch" />',
164+
'<input type="radio" role="menuitemradio" />',
165+
// DIVERGENCES captured as valid-for-us:
166+
// space-separated
167+
'<div role="combobox listbox" />',
168+
// case-insensitivity
169+
'<div role="COMBOBOX" />',
170+
// unknown role
171+
'<div role="foobar" />',
172+
],
173+
invalid: [
174+
{
175+
code: '<div role="slider" />',
176+
output: null,
177+
errors: [{ messageId: 'missingAttributes' }],
178+
},
179+
{
180+
code: '<div role="checkbox" />',
181+
output: null,
182+
errors: [{ messageId: 'missingAttributes' }],
183+
},
184+
{
185+
code: '<div role="heading" />',
186+
output: null,
187+
errors: [{ messageId: 'missingAttributes' }],
188+
},
189+
// DIVERGENCE: pairings NOT in our input+role whitelist stay flagged.
190+
// jsx-a11y/angular recognize more pairings via axobject-query.
191+
{
192+
code: '<input type="checkbox" role="radio" />',
193+
output: null,
194+
errors: [{ messageId: 'missingAttributes' }],
195+
},
196+
{
197+
code: '<input role="switch" />',
198+
output: null,
199+
errors: [{ messageId: 'missingAttributes' }],
200+
},
201+
],
202+
});

0 commit comments

Comments
 (0)