@@ -48,6 +48,24 @@ ruleTester.run('template-no-redundant-role', rule, {
4848 options : [ { checkAllHTMLElements : false } ] ,
4949 } ,
5050 '<template><input role="combobox"></template>' ,
51+ // <select multiple> has implicit role listbox, so combobox is not redundant.
52+ '<template><select role="combobox" multiple></select></template>' ,
53+ // <select size="5"> (size > 1) has implicit role listbox.
54+ '<template><select role="combobox" size="5"></select></template>' ,
55+ // Default <select> (no `multiple`, `size` absent or <= 1) has implicit
56+ // role "combobox" — an explicit `role="listbox"` overrides to listbox
57+ // and is NOT redundant.
58+ '<template><select role="listbox"></select></template>' ,
59+ '<template><select role="listbox" size="1"></select></template>' ,
60+ // Dynamic `multiple={{...}}` — can't determine implicit role statically,
61+ // so neither `role="combobox"` nor `role="listbox"` is flagged.
62+ '<template><select role="combobox" multiple={{this.isMulti}}></select></template>' ,
63+ '<template><select role="listbox" multiple={{this.isMulti}}></select></template>' ,
64+
65+ // Role-fallback: first recognised token wins. `role="tab button"` on
66+ // <button> resolves to `tab` (non-redundant — button's implicit is
67+ // `button`, not `tab`). WAI-ARIA §4.1 fallback-list semantics.
68+ '<template><button role="tab button"></button></template>' ,
5169 ] ,
5270 invalid : [
5371 {
@@ -69,6 +87,45 @@ ruleTester.run('template-no-redundant-role', rule, {
6987 } ,
7088 ] ,
7189 } ,
90+ // Non-landmark same-role redundancy — covered by jsx-a11y / vue-a11y too.
91+ {
92+ code : '<template><button role="button"></button></template>' ,
93+ output : '<template><button></button></template>' ,
94+ errors : [ { message : 'Use of redundant or invalid role: button on <button> detected.' } ] ,
95+ } ,
96+ {
97+ code : '<template><img role="img" /></template>' ,
98+ output : '<template><img /></template>' ,
99+ errors : [ { message : 'Use of redundant or invalid role: img on <img> detected.' } ] ,
100+ } ,
101+ {
102+ // Valueless `<select size>` — per HTML boolean-attr semantics, the
103+ // attribute value is an empty string; Number('') is 0; 0 is NOT > 1,
104+ // so the implicit role stays combobox. `role="combobox"` is therefore
105+ // redundant and must be flagged.
106+ code : '<template><select role="combobox" size></select></template>' ,
107+ output : '<template><select size></select></template>' ,
108+ errors : [
109+ {
110+ message : 'Use of redundant or invalid role: combobox on <select> detected.' ,
111+ } ,
112+ ] ,
113+ } ,
114+ {
115+ // Role-fallback: unknown leading token is skipped per ARIA §4.1.
116+ // `role="xxyxyz button"` resolves to `button`, which IS redundant on
117+ // <button>. Autofix drops the whole role attribute — the implicit
118+ // `button` role is preserved natively, so runtime semantics are
119+ // unchanged. Authors who wanted the `xxyxyz` fallback for some
120+ // reason can opt out via eslint-disable.
121+ code : '<template><button role="xxyxyz button"></button></template>' ,
122+ output : '<template><button></button></template>' ,
123+ errors : [
124+ {
125+ message : 'Use of redundant or invalid role: button on <button> detected.' ,
126+ } ,
127+ ] ,
128+ } ,
72129 {
73130 code : '<template><main role="main"></main></template>' ,
74131 output : '<template><main></main></template>' ,
@@ -155,6 +212,17 @@ hbsRuleTester.run('template-no-redundant-role', rule, {
155212 options : [ { checkAllHTMLElements : false } ] ,
156213 } ,
157214 '<ul class="list" role="combobox"></ul>' ,
215+ // <select> with `multiple` has implicit role "listbox", so role="combobox"
216+ // is not redundant (it disagrees with the implicit role, but that is for
217+ // other rules to catch — this rule only flags redundancy).
218+ '<select role="combobox" multiple></select>' ,
219+ // <select size="5"> (size > 1) has implicit role "listbox", same reasoning.
220+ '<select role="combobox" size="5"></select>' ,
221+ // Default <select> (no `multiple`, `size` absent or <= 1) has implicit
222+ // role "combobox" — explicit role="listbox" overrides to listbox and is
223+ // NOT redundant.
224+ '<select role="listbox"></select>' ,
225+ '<select role="listbox" size="1"></select>' ,
158226 ] ,
159227 invalid : [
160228 {
@@ -243,6 +311,30 @@ hbsRuleTester.run('template-no-redundant-role', rule, {
243311 '<select name="color" id="color" multiple><option value="default-color">black</option></select>' ,
244312 errors : [ { message : 'Use of redundant or invalid role: listbox on <select> detected.' } ] ,
245313 } ,
314+ {
315+ // <select> without `multiple` or `size` defaults to role "combobox".
316+ code : '<select role="combobox"></select>' ,
317+ output : '<select></select>' ,
318+ errors : [ { message : 'Use of redundant or invalid role: combobox on <select> detected.' } ] ,
319+ } ,
320+ {
321+ // size="1" still defaults to combobox (only size > 1 flips to listbox).
322+ code : '<select role="combobox" size="1"></select>' ,
323+ output : '<select size="1"></select>' ,
324+ errors : [ { message : 'Use of redundant or invalid role: combobox on <select> detected.' } ] ,
325+ } ,
326+ {
327+ // Case-insensitive match on <select>, combined with the implicit-role check.
328+ code : '<select role="COMBOBOX"></select>' ,
329+ output : '<select></select>' ,
330+ errors : [ { message : 'Use of redundant or invalid role: combobox on <select> detected.' } ] ,
331+ } ,
332+ {
333+ // Case-insensitive matching — ARIA role tokens compare as ASCII-case-insensitive.
334+ code : '<body role="DOCUMENT"></body>' ,
335+ output : '<body></body>' ,
336+ errors : [ { message : 'Use of redundant or invalid role: document on <body> detected.' } ] ,
337+ } ,
246338 {
247339 code : '<main role="main"></main>' ,
248340 output : '<main></main>' ,
0 commit comments