From 8fc6f6d10f2a7c4a068767b2c46e1bad8169e7ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20R=C3=B8ed?= Date: Tue, 21 Apr 2026 07:41:42 +0200 Subject: [PATCH 1/6] =?UTF-8?q?fix(template-no-redundant-role):=20lowercas?= =?UTF-8?q?e=20role;=20add=20 without `multiple` or `size > 1` has an implicit role of "combobox" per HTML-AAM §4.1. Before: is a combobox by default (per HTML-AAM §4.1 — unless `multiple` or + // a `size > 1` is set, in which case it's a listbox; both mappings are listed). + combobox: ['select'], columnheader: ['th'], complementary: ['aside'], contentinfo: ['footer'], @@ -125,7 +128,8 @@ module.exports = { let roleValue; if (roleAttr.value && roleAttr.value.type === 'GlimmerTextNode') { - roleValue = roleAttr.value.chars || ''; + // ARIA role tokens are compared ASCII-case-insensitively. + roleValue = (roleAttr.value.chars || '').toLowerCase(); } else { // Skip dynamic role values return; diff --git a/tests/lib/rules/template-no-redundant-role.js b/tests/lib/rules/template-no-redundant-role.js index 4d529b2fe1..6ff313bde3 100644 --- a/tests/lib/rules/template-no-redundant-role.js +++ b/tests/lib/rules/template-no-redundant-role.js @@ -243,6 +243,18 @@ hbsRuleTester.run('template-no-redundant-role', rule, { '', errors: [{ message: 'Use of redundant or invalid role: listbox on without `multiple` or `size` defaults to role "combobox". + code: '', + output: '', + errors: [{ message: 'Use of redundant or invalid role: combobox on 's implicit role is "combobox" only when neither `multiple` nor `size > 1` is present; otherwise it is "listbox". The previous commit added `combobox: ['select']` unconditionally, which caused false positives for (where combobox disagrees with the implicit listbox role and therefore is not redundant). Add a selectHasComboboxImplicitRole helper mirroring jsx-a11y's src/util/implicitRoles/select.js, and short-circuit the redundancy check for mapping lives in the HTML-AAM main conformance table, section 4, without a numbered subsection). Tests: - valid: - valid: - invalid: (size=1 → combobox) - invalid: (case + implicit) --- lib/rules/template-no-redundant-role.js | 36 +++++++++++++++++-- tests/lib/rules/template-no-redundant-role.js | 22 ++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/lib/rules/template-no-redundant-role.js b/lib/rules/template-no-redundant-role.js index 076defd718..a3248869bd 100644 --- a/lib/rules/template-no-redundant-role.js +++ b/lib/rules/template-no-redundant-role.js @@ -37,6 +37,25 @@ const ALLOWED_ELEMENT_ROLES = [ { name: 'input', role: 'combobox' }, ]; +// Per HTML-AAM, is a combobox by default (per HTML-AAM §4.1 — unless `multiple` or - // a `size > 1` is set, in which case it's a listbox; both mappings are listed). + // is only redundant when has implicit role listbox, so combobox is not redundant. + '', + // with `multiple` has implicit role "listbox", so role="combobox" + // is not redundant (it disagrees with the implicit role, but that is for + // other rules to catch — this rule only flags redundancy). + '', + // ', ], invalid: [ { @@ -249,6 +259,18 @@ hbsRuleTester.run('template-no-redundant-role', rule, { output: '', errors: [{ message: 'Use of redundant or invalid role: combobox on ', + output: '', + errors: [{ message: 'Use of redundant or invalid role: combobox on , combined with the implicit-role check. + code: '', + output: '', + errors: [{ message: 'Use of redundant or invalid role: combobox on flagged; === + // jsx-a11y: implicit role depends on `type`. Default (type=text) + // has implicit "textbox". So would be VALID. + // Our rule: VALID — ALLOWED_ELEMENT_ROLES includes {input, combobox}. + // Parity by coincidence. + '', + + // === Parity — has implicit role "combobox". + // Our rule: now INVALID for a default an implicit role of "listbox", so an + // explicit "combobox" is a genuine role override, not a redundancy. + '', + '', + ], + + invalid: [ + // === Upstream parity (invalid in jsx-a11y + ours) === + { + code: '', + output: '', + errors: [{ message: 'Use of redundant or invalid role: dialog on detected.' }], + }, + { + code: '', + output: '', + errors: [{ message: 'Use of redundant or invalid role: button on ', + '', + '
', + // DIVERGENCE — ul/ol list kept valid by design (see gts section). + '', + '
    ', + // DIVERGENCE — kept valid by design. + 'x', + // Parity — ', + ], + invalid: [ + { + code: '', + output: '', + errors: [{ message: 'Use of redundant or invalid role: button on