@@ -57,9 +57,6 @@ ruleTester.run('template-no-invalid-role', rule, {
5757 '<template><table role="textbox"></table></template>' ,
5858 '<template><div role="{{if this.inModal "dialog" "contentinfo" }}"></div></template>' ,
5959
60- // Missing VALID_ROLES entries: associationlistitemkey, associationlistitemvalue, cell
61- '<template><div role="associationlistitemkey">Key</div></template>' ,
62- '<template><div role="associationlistitemvalue">Value</div></template>' ,
6360 '<template><td role="cell">Data</td></template>' ,
6461
6562 // Case-insensitive role matching
@@ -71,9 +68,56 @@ ruleTester.run('template-no-invalid-role', rule, {
7168 code : '<template><div role="command interface"></div></template>' ,
7269 options : [ { catchNonexistentRoles : false } ] ,
7370 } ,
71+
72+ // DPUB-ARIA (doc-*) and Graphics-ARIA (graphics-*) are valid per aria-query.
73+ '<template><div role="doc-abstract">Abstract</div></template>' ,
74+ '<template><section role="doc-chapter"></section></template>' ,
75+ '<template><svg role="graphics-document"></svg></template>' ,
76+ '<template><svg role="graphics-object"></svg></template>' ,
77+
78+ // Whitespace-separated role fallback list — ARIA 1.2 §5.4. Each token
79+ // must individually be valid.
80+ '<template><div role="tabpanel row"></div></template>' ,
81+ '<template><svg role="graphics-document document"></svg></template>' ,
82+ '<template><section role="doc-appendix doc-bibliography"></section></template>' ,
83+
84+ // Role-fallback: `presentation`/`none` in a non-first position does NOT
85+ // flag on a semantic element — UAs pick the first recognised role per
86+ // §4.1. Here `button` resolves, `presentation` is an unused fallback.
87+ '<template><ul role="list presentation"></ul></template>' ,
88+ '<template><table role="grid none"></table></template>' ,
89+
90+ // ARIA 1.3 draft roles — not in aria-query 5.3.2 but spec-blessed, so
91+ // the rule accepts them via the inline allowlist.
92+ '<template><div role="associationlist"></div></template>' ,
93+ '<template><div role="associationlistitemkey"></div></template>' ,
94+ '<template><div role="associationlistitemvalue"></div></template>' ,
95+ '<template><div role="comment"></div></template>' ,
96+ '<template><div role="suggestion"></div></template>' ,
7497 ] ,
7598
7699 invalid : [
100+ // Common authoring confusion — `datepicker` looks like it could be a
101+ // role (it's a UI concept) but isn't in the ARIA registry. Same lookup
102+ // path as `role="invalid"`, exercised here for the more likely typo.
103+ {
104+ code : '<template><div role="datepicker"></div></template>' ,
105+ output : null ,
106+ errors : [ { messageId : 'invalid' } ] ,
107+ } ,
108+ // Empty / whitespace-only role attribute supplies no recognized role
109+ // token — flag as an authoring mistake. Aligns with jsx-a11y and
110+ // vue-a11y (both flag).
111+ {
112+ code : '<template><div role=""></div></template>' ,
113+ output : null ,
114+ errors : [ { messageId : 'invalid' } ] ,
115+ } ,
116+ {
117+ code : '<template><div role=" "></div></template>' ,
118+ output : null ,
119+ errors : [ { messageId : 'invalid' } ] ,
120+ } ,
77121 {
78122 code : `<template>
79123 <div role="invalid">Content</div>
@@ -144,6 +188,18 @@ ruleTester.run('template-no-invalid-role', rule, {
144188 { message : 'The role "presentation" should not be used on the semantic element <button>.' } ,
145189 ] ,
146190 } ,
191+ // Role-fallback: unknown leading token is skipped per §4.1, so the
192+ // first RECOGNISED role is `presentation` → flag on semantic element.
193+ // Uses catchNonexistentRoles: false so the unknown `xxyxyz` doesn't
194+ // intercept the check via the invalid-role path.
195+ {
196+ code : '<template><ul role="xxyxyz presentation"></ul></template>' ,
197+ output : null ,
198+ options : [ { catchNonexistentRoles : false } ] ,
199+ errors : [
200+ { message : 'The role "presentation" should not be used on the semantic element <ul>.' } ,
201+ ] ,
202+ } ,
147203 {
148204 code : '<template><button role="none"></button></template>' ,
149205 output : null ,
@@ -164,18 +220,20 @@ ruleTester.run('template-no-invalid-role', rule, {
164220 {
165221 code : '<template><div role="command interface"></div></template>' ,
166222 output : null ,
167- errors : [ { message : "Invalid ARIA role 'command interface '. Must be a valid ARIA role." } ] ,
223+ errors : [ { message : "Invalid ARIA role 'command'. Must be a valid ARIA role." } ] ,
168224 } ,
169225 {
170226 code : '<template><div role="COMMAND INTERFACE"></div></template>' ,
171227 output : null ,
172- errors : [ { message : "Invalid ARIA role 'COMMAND INTERFACE'. Must be a valid ARIA role." } ] ,
228+ // Validation is case-insensitive, but the error message echoes the
229+ // author-provided token verbatim so authors see their own text.
230+ errors : [ { message : "Invalid ARIA role 'COMMAND'. Must be a valid ARIA role." } ] ,
173231 } ,
174232 {
175233 code : '<template><div role="command interface"></div></template>' ,
176234 output : null ,
177235 options : [ { catchNonexistentRoles : true } ] ,
178- errors : [ { message : "Invalid ARIA role 'command interface '. Must be a valid ARIA role." } ] ,
236+ errors : [ { message : "Invalid ARIA role 'command'. Must be a valid ARIA role." } ] ,
179237 } ,
180238
181239 // Newly added SEMANTIC_ELEMENTS: presentation/none on iframe, video, audio
@@ -234,9 +292,6 @@ hbsRuleTester.run('template-no-invalid-role', rule, {
234292 '<AwesomeThing role="presentation"></AwesomeThing>' ,
235293 '<table role="textbox"></table>' ,
236294 '<div role="{{if this.inModal "dialog" "contentinfo" }}"></div>' ,
237- // Missing VALID_ROLES entries: associationlistitemkey, associationlistitemvalue, cell
238- '<div role="associationlistitemkey">Key</div>' ,
239- '<div role="associationlistitemvalue">Value</div>' ,
240295 '<td role="cell">Data</td>' ,
241296 // Case-insensitive role matching
242297 '<div role="Button">Click</div>' ,
@@ -247,6 +302,24 @@ hbsRuleTester.run('template-no-invalid-role', rule, {
247302 code : '<div role="command interface"></div>' ,
248303 options : [ { catchNonexistentRoles : false } ] ,
249304 } ,
305+
306+ // DPUB-ARIA (doc-*) and Graphics-ARIA (graphics-*) roles.
307+ '<div role="doc-abstract">Abstract</div>' ,
308+ '<section role="doc-chapter"></section>' ,
309+ '<svg role="graphics-document"></svg>' ,
310+
311+ // Whitespace-separated role fallback list.
312+ '<div role="tabpanel row"></div>' ,
313+ '<svg role="graphics-document document"></svg>' ,
314+ '<section role="doc-appendix doc-bibliography"></section>' ,
315+
316+ // ARIA 1.3 draft roles — not in aria-query 5.3.2 but spec-blessed, so
317+ // the rule accepts them via the inline allowlist.
318+ '<div role="associationlist"></div>' ,
319+ '<div role="associationlistitemkey"></div>' ,
320+ '<div role="associationlistitemvalue"></div>' ,
321+ '<div role="comment"></div>' ,
322+ '<div role="suggestion"></div>' ,
250323 ] ,
251324 invalid : [
252325 {
@@ -302,18 +375,20 @@ hbsRuleTester.run('template-no-invalid-role', rule, {
302375 {
303376 code : '<div role="command interface"></div>' ,
304377 output : null ,
305- errors : [ { message : "Invalid ARIA role 'command interface '. Must be a valid ARIA role." } ] ,
378+ errors : [ { message : "Invalid ARIA role 'command'. Must be a valid ARIA role." } ] ,
306379 } ,
307380 {
308381 code : '<div role="command interface"></div>' ,
309382 output : null ,
310383 options : [ { catchNonexistentRoles : true } ] ,
311- errors : [ { message : "Invalid ARIA role 'command interface '. Must be a valid ARIA role." } ] ,
384+ errors : [ { message : "Invalid ARIA role 'command'. Must be a valid ARIA role." } ] ,
312385 } ,
313386 {
314387 code : '<div role="COMMAND INTERFACE"></div>' ,
315388 output : null ,
316- errors : [ { message : "Invalid ARIA role 'COMMAND INTERFACE'. Must be a valid ARIA role." } ] ,
389+ // Validation is case-insensitive, but the error message echoes the
390+ // author-provided token verbatim so authors see their own text.
391+ errors : [ { message : "Invalid ARIA role 'COMMAND'. Must be a valid ARIA role." } ] ,
317392 } ,
318393 // Newly added SEMANTIC_ELEMENTS: presentation/none on iframe, video, audio, embed
319394 {
0 commit comments