diff --git a/docs/rules/template-require-mandatory-role-attributes.md b/docs/rules/template-require-mandatory-role-attributes.md
index 534c9189b8..e7b7521dc9 100644
--- a/docs/rules/template-require-mandatory-role-attributes.md
+++ b/docs/rules/template-require-mandatory-role-attributes.md
@@ -31,9 +31,39 @@ This rule **allows** the following:
` (missing attrs
+ // for both), we don't. Captured as valid below.
+ '
',
+
+ // === DIVERGENCE — case-insensitivity on role value ===
+ // jsx-a11y + vue + angular: lowercase the role value before lookup.
+ // `
` → INVALID (missing aria-expanded/controls).
+ // Our rule: passes the raw string; aria-query lookup misses → no flag.
+ '
',
+ '
',
+ ],
+
+ invalid: [
+ // === Upstream parity (invalid everywhere) ===
+ {
+ code: '
',
+ output: null,
+ errors: [{ messageId: 'missingAttributes' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ messageId: 'missingAttributes' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ messageId: 'missingAttributes' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ messageId: 'missingAttributes' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ messageId: 'missingAttributes' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ messageId: 'missingAttributes' }],
+ },
+
+ // === DIVERGENCE — partial attrs present, still missing one ===
+ // jsx-a11y flags `
`
+ // (missing aria-controls/aria-orientation/aria-valuenow).
+ // Our rule: also flags — missing-attrs list non-empty. Parity.
+ {
+ code: '
',
+ output: null,
+ errors: [{ messageId: 'missingAttributes' }],
+ },
+
+ // === Pairings NOT exempt — axobject-query does not list them ===
+ // Semantic-role exemption is driven by axobject-query's `elementAXObjects`
+ // + `AXObjectRoles` maps — see `isSemanticRoleElement()` in the rule
+ // source. Pairings the AX-tree data does not list (such as
+ // `input[type=checkbox] role=radio` or `input[type=radio] role=switch`)
+ // fall through to the normal required-attribute check and are flagged
+ // for missing `aria-checked`.
+ {
+ code: '
',
+ output: null,
+ errors: [{ messageId: 'missingAttributes' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ messageId: 'missingAttributes' }],
+ },
+ // Bare `
` (no `type`) has no exempt pairing either —
+ // the element defaults to `type=text`, which axobject-query does not map
+ // to the switch role.
+ {
+ code: '
',
+ output: null,
+ errors: [{ messageId: 'missingAttributes' }],
+ },
+ ],
+});
+
+const hbsRuleTester = new RuleTester({
+ parser: require.resolve('ember-eslint-parser/hbs'),
+ parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
+});
+
+hbsRuleTester.run('audit:role-has-required-aria (hbs)', rule, {
+ valid: [
+ '
',
+ '
',
+ '
',
+ '
',
+ // Parity: axobject-query-backed semantic-role exemptions.
+ '
',
+ '
',
+ // DIVERGENCES captured as valid-for-us:
+ // space-separated
+ '
',
+ // case-insensitivity
+ '
',
+ // unknown role
+ '
',
+ ],
+ invalid: [
+ {
+ code: '
',
+ output: null,
+ errors: [{ messageId: 'missingAttributes' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ messageId: 'missingAttributes' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ messageId: 'missingAttributes' }],
+ },
+ // DIVERGENCE: pairings NOT in our input+role whitelist stay flagged.
+ // jsx-a11y/angular recognize more pairings via axobject-query.
+ {
+ code: '
',
+ output: null,
+ errors: [{ messageId: 'missingAttributes' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ messageId: 'missingAttributes' }],
+ },
+ ],
+});
diff --git a/tests/lib/rules/template-require-mandatory-role-attributes.js b/tests/lib/rules/template-require-mandatory-role-attributes.js
index 43af97d30a..1e2b26d3dd 100644
--- a/tests/lib/rules/template-require-mandatory-role-attributes.js
+++ b/tests/lib/rules/template-require-mandatory-role-attributes.js
@@ -25,6 +25,25 @@ ruleTester.run('template-require-mandatory-role-attributes', rule, {
'
{{foo-component role="button"}}',
'
{{foo-component role="unknown"}}',
'
{{foo-component role=role}}',
+
+ // Semantic inputs supply required ARIA state natively. Exempt pairings
+ // are looked up via axobject-query's elementAXObjects + AXObjectRoles.
+
+ // checkbox/switch: aria-checked supplied via native `checked` state.
+ '
',
+ '
',
+ '
',
+ '
',
+ '
',
+
+ // slider: aria-valuenow supplied via native `value` (axobject-query SliderRole).
+ '
',
+
+ // Classic Ember {{input type=... role=...}} helper renders a native
+ //
; same axobject-query lookup applies.
+ '
{{input type="checkbox" role="switch"}}',
+ '
{{input type="Checkbox" role="switch"}}',
+ '
{{input type="range" role="slider"}}',
],
invalid: [
@@ -75,6 +94,60 @@ ruleTester.run('template-require-mandatory-role-attributes', rule, {
output: null,
errors: [{ message: 'The attribute aria-checked is required by the role checkbox' }],
},
+
+ // Undocumented {input type, role} pairings are NOT exempted.
+ {
+ code: '
',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role radio' }],
+ },
+ // {{input}} helper with off-whitelist role is flagged too.
+ {
+ code: '
{{input type="text" role="switch"}}',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role switch' }],
+ },
+ {
+ code: '
{{input type="checkbox" role="radio"}}',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role radio' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role switch' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role checkbox' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role switch' }],
+ },
+ {
+ // No `type` attribute; defaults to text.
+ code: '
',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role switch' }],
+ },
+
+ // menuitemcheckbox / menuitemradio on
are NOT exempted —
+ // axobject-query's MenuItemCheckBoxRole / MenuItemRadioRole lists only
+ // an ARIA concept, no HTML concept for
. Flagged for missing
+ // aria-checked.
+ {
+ code: '
',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role menuitemcheckbox' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role menuitemradio' }],
+ },
],
});
@@ -105,6 +178,20 @@ hbsRuleTester.run('template-require-mandatory-role-attributes', rule, {
'{{foo-component role="button"}}',
'{{foo-component role="unknown"}}',
'{{foo-component role=role}}',
+
+ // Semantic inputs supply required ARIA state natively (via axobject-query
+ // elementAXObjects lookup).
+ '
',
+ '
',
+ '
',
+ '
',
+ '
',
+ '
',
+
+ // Classic Ember {{input}} helper renders a native
; same lookup.
+ '{{input type="checkbox" role="switch"}}',
+ '{{input type="Checkbox" role="switch"}}',
+ '{{input type="range" role="slider"}}',
],
invalid: [
{
@@ -146,5 +233,57 @@ hbsRuleTester.run('template-require-mandatory-role-attributes', rule, {
output: null,
errors: [{ message: 'The attribute aria-checked is required by the role checkbox' }],
},
+
+ // Undocumented {input type, role} pairings are NOT exempted.
+ {
+ code: '
',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role radio' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role switch' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role checkbox' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role switch' }],
+ },
+ {
+ // No `type` attribute; defaults to text.
+ code: '
',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role switch' }],
+ },
+
+ // {{input}} helper with off-whitelist role is flagged too.
+ {
+ code: '{{input type="text" role="switch"}}',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role switch' }],
+ },
+ {
+ code: '{{input type="checkbox" role="radio"}}',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role radio' }],
+ },
+
+ // menuitemcheckbox / menuitemradio on
are NOT exempted.
+ {
+ code: '
',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role menuitemcheckbox' }],
+ },
+ {
+ code: '
',
+ output: null,
+ errors: [{ message: 'The attribute aria-checked is required by the role menuitemradio' }],
+ },
],
});