Skip to content

Commit 999a107

Browse files
committed
fix(template-require-mandatory-role-attributes): extend native-checked exemption to {{input}} helper
Two changes: 1. `getInputType` now recognises the classic Ember `{{input type=...}}` helper as an input host, in addition to the native `<input>` element. The `GlimmerMustacheStatement` handler is updated to pass `node` into `getMissingRequiredAttributes`, matching the element-node handler. 2. Reverts the defensive `role.toLowerCase()` in the whitelist-key build from the previous commit — it was dead code. The caller (`getMissingRequiredAttributes`) short-circuits via aria-query's case-sensitive `roles.get(role)`, so `role` is guaranteed lowercase by the time `isNativelyChecked` runs. Rule-wide role case- insensitivity is not in scope for this PR; that is ember-cli#2728. Tests adjusted accordingly: drop mixed-case-role valid tests (they passed for the wrong reason — via the case-sensitivity short-circuit in aria-query, not via the whitelist). Add coverage for `{{input}}` helper on both gts and hbs parsers, including off-whitelist pairings that must still flag.
1 parent 7af3c2f commit 999a107

2 files changed

Lines changed: 70 additions & 23 deletions

File tree

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

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,43 @@ const NATIVELY_CHECKED_INPUT_ROLE_PAIRS = new Set([
4242
'radio:menuitemradio',
4343
]);
4444

45+
// Reads the `type` keyword from a native <input> element OR a classic
46+
// `{{input type=...}}` helper invocation (the built-in Ember helper renders
47+
// a native <input>). Returns the lowercased value, or undefined if the node
48+
// is not an input-like host or the value is dynamic.
4549
function getInputType(node) {
46-
if (node?.tag !== 'input') {
50+
if (node?.type === 'GlimmerElementNode') {
51+
if (node.tag !== 'input') {
52+
return undefined;
53+
}
54+
const typeAttr = node.attributes?.find((a) => a.name === 'type');
55+
if (typeAttr?.value?.type === 'GlimmerTextNode') {
56+
return typeAttr.value.chars?.toLowerCase();
57+
}
4758
return undefined;
4859
}
49-
const typeAttr = node.attributes?.find((a) => a.name === 'type');
50-
if (typeAttr?.value?.type === 'GlimmerTextNode') {
51-
// HTML input `type` keywords are ASCII case-insensitive.
52-
return typeAttr.value.chars?.toLowerCase();
60+
if (node?.type === 'GlimmerMustacheStatement') {
61+
if (node.path?.original !== 'input') {
62+
return undefined;
63+
}
64+
const typePair = node.hash?.pairs?.find((pair) => pair.key === 'type');
65+
if (typePair?.value?.type === 'GlimmerStringLiteral') {
66+
return typePair.value.value?.toLowerCase();
67+
}
68+
return undefined;
5369
}
5470
return undefined;
5571
}
5672

5773
function isNativelyChecked(node, role) {
5874
const type = getInputType(node);
59-
if (!type || typeof role !== 'string') {
75+
if (!type) {
6076
return false;
6177
}
62-
// ARIA role tokens compare ASCII-case-insensitively.
63-
return NATIVELY_CHECKED_INPUT_ROLE_PAIRS.has(`${type}:${role.toLowerCase()}`);
78+
// `role` is guaranteed to be a lowercase aria-query role key by the time we
79+
// get here (getMissingRequiredAttributes short-circuits via `roles.get(role)`
80+
// which is case-sensitive), so no lowercasing needed on the whitelist key.
81+
return NATIVELY_CHECKED_INPUT_ROLE_PAIRS.has(`${type}:${role}`);
6482
}
6583

6684
function getMissingRequiredAttributes(role, foundAriaAttributes, node) {
@@ -156,7 +174,11 @@ module.exports = {
156174
.filter((pair) => pair.key.startsWith('aria-'))
157175
.map((pair) => pair.key);
158176

159-
const missingRequiredAttributes = getMissingRequiredAttributes(role, foundAriaAttributes);
177+
const missingRequiredAttributes = getMissingRequiredAttributes(
178+
role,
179+
foundAriaAttributes,
180+
node
181+
);
160182

161183
if (missingRequiredAttributes) {
162184
reportMissingAttributes(node, role, missingRequiredAttributes);

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

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,18 @@ ruleTester.run('template-require-mandatory-role-attributes', rule, {
3636
'<template><input type="checkbox" role="checkbox" /></template>',
3737

3838
// 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.)
3941
'<template><input type="Checkbox" role="switch" /></template>',
42+
'<template><input type="CHECKBOX" role="switch" /></template>',
4043

41-
// ARIA role tokens are ASCII case-insensitive — mixed-case roles are
42-
// exempted on the same pairings.
43-
'<template><input type="checkbox" role="SWITCH" /></template>',
44-
'<template><input type="checkbox" role="Switch" /></template>',
45-
'<template><input type="CHECKBOX" role="MENUITEMCHECKBOX" /></template>',
46-
'<template><input type="radio" role="Radio" /></template>',
47-
'<template><input type="RADIO" role="menuitemRadio" /></template>',
44+
// Classic Ember {{input type=... role=...}} helper renders a native
45+
// <input> and is exempted on the same whitelist as the angle-bracket form.
46+
'<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.
50+
'<template>{{input type="Checkbox" role="switch"}}</template>',
4851
],
4952

5053
invalid: [
@@ -102,6 +105,17 @@ ruleTester.run('template-require-mandatory-role-attributes', rule, {
102105
output: null,
103106
errors: [{ message: 'The attribute aria-checked is required by the role radio' }],
104107
},
108+
// {{input}} helper with off-whitelist role is flagged too.
109+
{
110+
code: '<template>{{input type="text" role="switch"}}</template>',
111+
output: null,
112+
errors: [{ message: 'The attribute aria-checked is required by the role switch' }],
113+
},
114+
{
115+
code: '<template>{{input type="checkbox" role="radio"}}</template>',
116+
output: null,
117+
errors: [{ message: 'The attribute aria-checked is required by the role radio' }],
118+
},
105119
{
106120
code: '<template><input type="radio" role="switch" /></template>',
107121
output: null,
@@ -163,14 +177,13 @@ hbsRuleTester.run('template-require-mandatory-role-attributes', rule, {
163177

164178
// HTML `type` is ASCII case-insensitive; `Checkbox` must match `checkbox`.
165179
'<input type="Checkbox" role="switch" />',
180+
'<input type="CHECKBOX" role="switch" />',
166181

167-
// ARIA role tokens are ASCII case-insensitive — mixed-case roles are
168-
// exempted on the same pairings.
169-
'<input type="checkbox" role="SWITCH" />',
170-
'<input type="checkbox" role="Switch" />',
171-
'<input type="CHECKBOX" role="MENUITEMCHECKBOX" />',
172-
'<input type="radio" role="Radio" />',
173-
'<input type="RADIO" role="menuitemRadio" />',
182+
// Classic Ember {{input}} helper renders a native <input>; same whitelist.
183+
'{{input type="checkbox" role="switch"}}',
184+
'{{input type="checkbox" role="menuitemcheckbox"}}',
185+
'{{input type="radio" role="menuitemradio"}}',
186+
'{{input type="Checkbox" role="switch"}}',
174187
],
175188
invalid: [
176189
{
@@ -240,5 +253,17 @@ hbsRuleTester.run('template-require-mandatory-role-attributes', rule, {
240253
output: null,
241254
errors: [{ message: 'The attribute aria-checked is required by the role switch' }],
242255
},
256+
257+
// {{input}} helper with off-whitelist role is flagged too.
258+
{
259+
code: '{{input type="text" role="switch"}}',
260+
output: null,
261+
errors: [{ message: 'The attribute aria-checked is required by the role switch' }],
262+
},
263+
{
264+
code: '{{input type="checkbox" role="radio"}}',
265+
output: null,
266+
errors: [{ message: 'The attribute aria-checked is required by the role radio' }],
267+
},
243268
],
244269
});

0 commit comments

Comments
 (0)