Skip to content

Commit a180d27

Browse files
committed
test: add Phase 3 audit fixture translating role-has-required-aria peer cases
Translates 33 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 Fixture documents parity after this fix: - Case-insensitive role comparison (<div role="COMBOBOX"> flagged). - Whitespace-separated roles: first recognised token is validated (ARIA 1.2 §4.1 role-fallback semantics). Divergence from jsx-a11y, which validates every recognised token; documented inline. The semantic input+role exception (input[type=checkbox] role=switch) remains flagged here — that fix is on a separate branch.
1 parent 892ec9a commit a180d27

1 file changed

Lines changed: 204 additions & 0 deletions

File tree

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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+
// === DIVERGENCE — <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: INVALID — we treat every element generically and `switch` has
52+
// `aria-checked` as a required prop. Captured in invalid section below.
53+
54+
// === Partial parity — space-separated role tokens ===
55+
// jsx-a11y + vue: split on whitespace, validate EACH recognised token.
56+
// Our rule: splits on whitespace, validates only the FIRST recognised
57+
// token (ARIA 1.2 §4.1 role-fallback semantics — UA picks the first
58+
// recognised role). So `<div role="button combobox">` — which has
59+
// "button" as the first recognised token (no required attrs) —
60+
// remains valid for us but jsx-a11y would flag it for missing
61+
// combobox attrs.
62+
'<template><div role="button combobox" /></template>',
63+
// Both-token case where the first token HAS no required attrs: valid
64+
// for us, invalid for jsx-a11y.
65+
'<template><div role="heading button" aria-level="2" /></template>',
66+
],
67+
68+
invalid: [
69+
// === Upstream parity (invalid everywhere) ===
70+
{
71+
code: '<template><div role="slider" /></template>',
72+
output: null,
73+
errors: [{ messageId: 'missingAttributes' }],
74+
},
75+
{
76+
code: '<template><div role="checkbox" /></template>',
77+
output: null,
78+
errors: [{ messageId: 'missingAttributes' }],
79+
},
80+
{
81+
code: '<template><div role="combobox" /></template>',
82+
output: null,
83+
errors: [{ messageId: 'missingAttributes' }],
84+
},
85+
{
86+
code: '<template><div role="scrollbar" /></template>',
87+
output: null,
88+
errors: [{ messageId: 'missingAttributes' }],
89+
},
90+
{
91+
code: '<template><div role="heading" /></template>',
92+
output: null,
93+
errors: [{ messageId: 'missingAttributes' }],
94+
},
95+
{
96+
code: '<template><div role="option" /></template>',
97+
output: null,
98+
errors: [{ messageId: 'missingAttributes' }],
99+
},
100+
101+
// === Partial parity — partial attrs present, still missing one ===
102+
// jsx-a11y flags `<div role="scrollbar" aria-valuemax aria-valuemin />`
103+
// (missing aria-controls/aria-orientation/aria-valuenow).
104+
// Our rule: also flags — missing-attrs list non-empty. Parity.
105+
{
106+
code: '<template><div role="combobox" aria-controls="x" /></template>',
107+
output: null,
108+
errors: [{ messageId: 'missingAttributes' }],
109+
},
110+
111+
// === Parity — case-insensitive role comparison ===
112+
// jsx-a11y + vue + angular lowercase the role value before lookup.
113+
// Our rule now does the same, so `<div role="COMBOBOX" />` → INVALID
114+
// (missing aria-expanded / aria-controls).
115+
{
116+
code: '<template><div role="COMBOBOX" /></template>',
117+
output: null,
118+
errors: [{ messageId: 'missingAttributes' }],
119+
},
120+
{
121+
code: '<template><div role="SLIDER" /></template>',
122+
output: null,
123+
errors: [{ messageId: 'missingAttributes' }],
124+
},
125+
126+
// === Parity — whitespace-separated roles, first recognised validated ===
127+
// `<div role="combobox listbox">` — both tokens are recognised roles
128+
// with required attrs. Per ARIA role-fallback semantics we validate
129+
// the first recognised token (combobox). jsx-a11y validates every
130+
// token; both plugins end up flagging this same code (though our
131+
// error names `combobox`, jsx-a11y may cite all missing attrs).
132+
{
133+
code: '<template><div role="combobox listbox" /></template>',
134+
output: null,
135+
errors: [{ messageId: 'missingAttributes' }],
136+
},
137+
138+
// === DIVERGENCE — input[type=checkbox] role="switch" ===
139+
// jsx-a11y / vue / angular: VALID (semantic exception).
140+
// Our rule: INVALID (missing aria-checked). FALSE POSITIVE.
141+
// (This PR does not fix the semantic-input exception; separate
142+
// fix lives on fix/role-required-aria-checkbox-switch.)
143+
{
144+
code: '<template><input type="checkbox" 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+
// Partial-parity: role-fallback validates only the first recognised
163+
// token. `<div role="button combobox">` is valid for us (first
164+
// recognised token "button" needs no attrs) but flagged by jsx-a11y.
165+
'<div role="button combobox" />',
166+
// unknown role
167+
'<div role="foobar" />',
168+
],
169+
invalid: [
170+
{
171+
code: '<div role="slider" />',
172+
output: null,
173+
errors: [{ messageId: 'missingAttributes' }],
174+
},
175+
{
176+
code: '<div role="checkbox" />',
177+
output: null,
178+
errors: [{ messageId: 'missingAttributes' }],
179+
},
180+
{
181+
code: '<div role="heading" />',
182+
output: null,
183+
errors: [{ messageId: 'missingAttributes' }],
184+
},
185+
// Parity — case-insensitive comparison.
186+
{
187+
code: '<div role="COMBOBOX" />',
188+
output: null,
189+
errors: [{ messageId: 'missingAttributes' }],
190+
},
191+
// Parity — whitespace-separated roles, first recognised validated.
192+
{
193+
code: '<div role="combobox listbox" />',
194+
output: null,
195+
errors: [{ messageId: 'missingAttributes' }],
196+
},
197+
// DIVERGENCE: semantic input exception — jsx-a11y/vue/angular say VALID.
198+
{
199+
code: '<input type="checkbox" role="switch" />',
200+
output: null,
201+
errors: [{ messageId: 'missingAttributes' }],
202+
},
203+
],
204+
});

0 commit comments

Comments
 (0)