Skip to content

Commit 614446f

Browse files
committed
fix(template-require-mandatory-role-attributes): drop undocumented input+role pairings
Runtime verification of axobject-query 4.1 showed that MenuItemCheckBoxRole and MenuItemRadioRole list only ARIA concepts, not HTML concepts — there is no spec or library basis for treating <input type=checkbox role= menuitemcheckbox> or <input type=radio role=menuitemradio> as native- checked. jsx-a11y's isSemanticRoleElement flags both; angular-eslint also flags via axobject-query. Earlier commit incorrectly cited axobject-query's elementAXObjects for these pairings. Whitelist is now the three pairings that axobject-query actually backs: checkbox:checkbox, checkbox:switch, radio:radio. Moved menuitem pairings into the invalid test set (they flag for missing aria-checked, consistent with jsx-a11y + angular). Updated docs example and audit-fixture comments.
1 parent 999a107 commit 614446f

4 files changed

Lines changed: 57 additions & 38 deletions

File tree

docs/rules/template-require-mandatory-role-attributes.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ This rule **allows** the following:
3232
<CustomComponent role="checkbox" aria-required="true" aria-checked="false" />
3333
{{some-component role="heading" aria-level="2"}}
3434
35-
{{! <input type="checkbox|radio"> supplies aria-checked natively for roles that require it. }}
35+
{{! <input type="checkbox|radio"> supplies aria-checked natively for matching roles. }}
3636
<input type="checkbox" role="switch" />
37-
<input type="checkbox" role="menuitemcheckbox" />
38-
<input type="radio" role="menuitemradio" />
37+
<input type="checkbox" role="checkbox" />
38+
<input type="radio" role="radio" />
3939
</template>
4040
```
4141

lib/rules/template-require-mandatory-role-attributes.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,14 @@ function getStaticRoleFromMustache(node) {
2929
}
3030

3131
// `{input type}:{role}` pairings where the required `aria-checked` is supplied
32-
// natively by the semantic <input>. Pairings match axobject-query's
33-
// `elementAXObjects` for the input-role combinations and the WAI-ARIA APG
34-
// Switch pattern (https://www.w3.org/WAI/ARIA/apg/patterns/switch/).
35-
// Undocumented pairings (e.g. `input[type=checkbox] role=radio`) are NOT
36-
// exempted.
32+
// natively by the semantic <input>. Each pairing is backed by axobject-query's
33+
// `elementAXObjects` (CheckBoxRole / SwitchRole / RadioButtonRole all list a
34+
// matching HTML concept for <input type=checkbox|radio>) and/or the WAI-ARIA
35+
// APG Switch pattern (https://www.w3.org/WAI/ARIA/apg/patterns/switch/).
3736
const NATIVELY_CHECKED_INPUT_ROLE_PAIRS = new Set([
3837
'checkbox:checkbox',
3938
'checkbox:switch',
40-
'checkbox:menuitemcheckbox',
4139
'radio:radio',
42-
'radio:menuitemradio',
4340
]);
4441

4542
// Reads the `type` keyword from a native <input> element OR a classic

tests/audit/role-has-required-aria/peer-parity.js

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,28 @@ ruleTester.run('audit:role-has-required-aria (gts)', rule, {
4444
'<template><div role="foobar" /></template>',
4545

4646
// === 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.
47+
// jsx-a11y: VALID via `isSemanticRoleElement` (axobject-query SwitchRole
48+
// lists <input type=checkbox> as a related HTML concept).
49+
// vue-a11y: VALID via explicit {role: switch, type: checkbox} carve-out
50+
// in `filterRequiredPropsExceptions`.
51+
// angular: VALID via `isSemanticRoleElement`.
5152
// 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.
53+
// (checkbox+switch per APG Switch + axobject-query SwitchRole).
54+
// Also covers checkbox+checkbox and radio+radio (redundant-but-valid
55+
// per axobject-query CheckBoxRole / RadioButtonRole).
5556
'<template><input type="checkbox" role="switch" /></template>',
5657
'<template><input type="checkbox" role="checkbox" /></template>',
57-
'<template><input type="checkbox" role="menuitemcheckbox" /></template>',
5858
'<template><input type="radio" role="radio" /></template>',
59-
'<template><input type="radio" role="menuitemradio" /></template>',
6059
// HTML type keyword values are ASCII case-insensitive, so the whitelist
6160
// also matches uppercase `type` values.
6261
'<template><input type="CHECKBOX" role="switch" /></template>',
6362

63+
// === DIVERGENCE — input + menuitemcheckbox/menuitemradio ===
64+
// Neither axobject-query's MenuItemCheckBoxRole nor MenuItemRadioRole
65+
// lists an <input> HTML concept; they only have ARIA concepts. So
66+
// jsx-a11y / angular flag these pairings. Our rule also flags (captured
67+
// in the `invalid` section below).
68+
6469
// === DIVERGENCE — space-separated role tokens ===
6570
// jsx-a11y + vue: split on whitespace, validate each token. If every token
6671
// is a valid role, require attrs for each.

tests/lib/rules/template-require-mandatory-role-attributes.js

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,18 @@ ruleTester.run('template-require-mandatory-role-attributes', rule, {
2626
'<template>{{foo-component role="unknown"}}</template>',
2727
'<template>{{foo-component role=role}}</template>',
2828

29-
// Semantic inputs supply aria-checked natively; the role is satisfied
30-
// without an explicit aria-checked attribute. Documented accessible
31-
// patterns: https://www.w3.org/WAI/ARIA/apg/patterns/switch/#keyboardinteraction
29+
// Semantic inputs supply aria-checked natively. Exempt pairings match
30+
// axobject-query's elementAXObjects (CheckBoxRole/SwitchRole/RadioButtonRole
31+
// each list an <input type=checkbox|radio> HTML concept) + APG Switch.
3232
'<template><input type="checkbox" role="switch" /></template>',
33-
'<template><input type="checkbox" role="menuitemcheckbox" /></template>',
34-
'<template><input type="radio" role="menuitemradio" /></template>',
3533
'<template><input type="radio" role="radio" /></template>',
3634
'<template><input type="checkbox" role="checkbox" /></template>',
37-
38-
// HTML `type` is ASCII case-insensitive; `Checkbox` must match `checkbox`.
39-
// (The `type` attribute IS lowercased at the whitelist-key boundary; role
40-
// tokens are NOT — rule-wide role case-insensitivity lands in #2728.)
4135
'<template><input type="Checkbox" role="switch" /></template>',
4236
'<template><input type="CHECKBOX" role="switch" /></template>',
4337

4438
// Classic Ember {{input type=... role=...}} helper renders a native
4539
// <input> and is exempted on the same whitelist as the angle-bracket form.
4640
'<template>{{input type="checkbox" role="switch"}}</template>',
47-
'<template>{{input type="checkbox" role="menuitemcheckbox"}}</template>',
48-
'<template>{{input type="radio" role="menuitemradio"}}</template>',
49-
// `type` case-insensitivity works on the mustache form too.
5041
'<template>{{input type="Checkbox" role="switch"}}</template>',
5142
],
5243

@@ -137,6 +128,23 @@ ruleTester.run('template-require-mandatory-role-attributes', rule, {
137128
output: null,
138129
errors: [{ message: 'The attribute aria-checked is required by the role switch' }],
139130
},
131+
132+
// menuitemcheckbox / menuitemradio on <input> are NOT exempted —
133+
// axobject-query's MenuItemCheckBoxRole / MenuItemRadioRole lists only
134+
// an ARIA concept, no HTML concept for <input>. Flagged for missing
135+
// aria-checked.
136+
{
137+
code: '<template><input type="checkbox" role="menuitemcheckbox" /></template>',
138+
output: null,
139+
errors: [
140+
{ message: 'The attribute aria-checked is required by the role menuitemcheckbox' },
141+
],
142+
},
143+
{
144+
code: '<template><input type="radio" role="menuitemradio" /></template>',
145+
output: null,
146+
errors: [{ message: 'The attribute aria-checked is required by the role menuitemradio' }],
147+
},
140148
],
141149
});
142150

@@ -168,21 +176,16 @@ hbsRuleTester.run('template-require-mandatory-role-attributes', rule, {
168176
'{{foo-component role="unknown"}}',
169177
'{{foo-component role=role}}',
170178

171-
// Semantic inputs supply aria-checked natively.
179+
// Semantic inputs supply aria-checked natively. Exempt pairings match
180+
// axobject-query's elementAXObjects + APG Switch pattern.
172181
'<input type="checkbox" role="switch" />',
173-
'<input type="checkbox" role="menuitemcheckbox" />',
174-
'<input type="radio" role="menuitemradio" />',
175182
'<input type="radio" role="radio" />',
176183
'<input type="checkbox" role="checkbox" />',
177-
178-
// HTML `type` is ASCII case-insensitive; `Checkbox` must match `checkbox`.
179184
'<input type="Checkbox" role="switch" />',
180185
'<input type="CHECKBOX" role="switch" />',
181186

182187
// Classic Ember {{input}} helper renders a native <input>; same whitelist.
183188
'{{input type="checkbox" role="switch"}}',
184-
'{{input type="checkbox" role="menuitemcheckbox"}}',
185-
'{{input type="radio" role="menuitemradio"}}',
186189
'{{input type="Checkbox" role="switch"}}',
187190
],
188191
invalid: [
@@ -265,5 +268,19 @@ hbsRuleTester.run('template-require-mandatory-role-attributes', rule, {
265268
output: null,
266269
errors: [{ message: 'The attribute aria-checked is required by the role radio' }],
267270
},
271+
272+
// menuitemcheckbox / menuitemradio on <input> are NOT exempted.
273+
{
274+
code: '<input type="checkbox" role="menuitemcheckbox" />',
275+
output: null,
276+
errors: [
277+
{ message: 'The attribute aria-checked is required by the role menuitemcheckbox' },
278+
],
279+
},
280+
{
281+
code: '<input type="radio" role="menuitemradio" />',
282+
output: null,
283+
errors: [{ message: 'The attribute aria-checked is required by the role menuitemradio' }],
284+
},
268285
],
269286
});

0 commit comments

Comments
 (0)