@@ -52,7 +52,8 @@ ruleTester.run('template-interactive-supports-focus', rule, {
5252 '<template><div role="treeitem" tabindex="0"></div></template>' ,
5353 // tabindex="-1" is also sufficient — the role still has a focus target.
5454 '<template><div role="button" tabindex="-1"></div></template>' ,
55- // Dynamic tabindex satisfies the check (the attribute is present).
55+ // Dynamic tabindex satisfies the check (runtime value unknown — give
56+ // benefit of the doubt; the runtime value may be a valid number).
5657 '<template><div role="button" tabindex={{this.ti}}></div></template>' ,
5758
5859 // === Interactive role on a non-focusable host but contenteditable is truthy. ===
@@ -130,6 +131,20 @@ ruleTester.run('template-interactive-supports-focus', rule, {
130131 output : null ,
131132 errors : [ { messageId : 'focusable' , data : { tag : 'div' , role : 'button' } } ] ,
132133 } ,
134+ // Bare-mustache falsy on tabindex (rows t6, t7) — Glimmer omits the
135+ // attribute at runtime, so tabindex is NOT actually present and does not
136+ // satisfy the focus requirement. AST-presence check would have missed
137+ // these (false negatives — rule silently let invalid templates through).
138+ {
139+ code : '<template><div role="button" tabindex={{false}}></div></template>' ,
140+ output : null ,
141+ errors : [ { messageId : 'focusable' , data : { tag : 'div' , role : 'button' } } ] ,
142+ } ,
143+ {
144+ code : '<template><div role="button" tabindex={{null}}></div></template>' ,
145+ output : null ,
146+ errors : [ { messageId : 'focusable' , data : { tag : 'div' , role : 'button' } } ] ,
147+ } ,
133148 // Role-fallback: UAs walk past unknown leading tokens to the first
134149 // recognised role (`button` here). Rule should require focusability.
135150 // LLM guardrail: models sometimes emit speculative unknown-first lists.
0 commit comments