@@ -46,6 +46,19 @@ ruleTester.run('template-anchor-has-content', rule, {
4646 code : '<template><a href="/x"><img alt={{@alt}} /></a></template>' ,
4747 } ,
4848
49+ // <img alt={{"…"}}> — static string in mustache; non-empty alt counts as
50+ // accessible content.
51+ {
52+ filename : 'test.gjs' ,
53+ code : '<template><a href="/x"><img alt={{"Search"}} /></a></template>' ,
54+ } ,
55+
56+ // aria-label with a non-empty mustache string literal is a valid name.
57+ {
58+ filename : 'test.gjs' ,
59+ code : '<template><a href="/x" aria-label={{"Close"}} /></template>' ,
60+ } ,
61+
4962 // Component invocation (PascalCase) — not a plain HTML anchor, out of scope.
5063 { filename : 'test.gjs' , code : '<template><Link href="/x" /></template>' } ,
5164
@@ -101,6 +114,12 @@ ruleTester.run('template-anchor-has-content', rule, {
101114 filename : 'test.gjs' ,
102115 code : '<template><a href="/x" aria-hidden={{true}}></a></template>' ,
103116 } ,
117+ // aria-hidden={{"true"}} — string-literal mustache, resolved statically to
118+ // "true"; anchor is hidden from the a11y tree, check does not apply.
119+ {
120+ filename : 'test.gjs' ,
121+ code : '<template><a href="/x" aria-hidden={{"true"}} /></template>' ,
122+ } ,
104123
105124 // Scope-shadowed lowercase `a` (local binding in GJS) — not the native
106125 // HTML anchor, so the rule does not validate it. `isNativeElement`
@@ -185,6 +204,21 @@ ruleTester.run('template-anchor-has-content', rule, {
185204 output : null ,
186205 errors : [ { messageId : 'anchorHasContent' } ] ,
187206 } ,
207+ // aria-label={{""}} — static empty string in mustache is NOT a name.
208+ {
209+ filename : 'test.gjs' ,
210+ code : '<template><a href="/x" aria-label={{""}} /></template>' ,
211+ output : null ,
212+ errors : [ { messageId : 'anchorHasContent' } ] ,
213+ } ,
214+ // <img alt={{""}}> — static empty string in mustache is decorative;
215+ // no accessible name contributed.
216+ {
217+ filename : 'test.gjs' ,
218+ code : '<template><a href="/x"><img alt={{""}} /></a></template>' ,
219+ output : null ,
220+ errors : [ { messageId : 'anchorHasContent' } ] ,
221+ } ,
188222 // ` ` is normalized to space — `aria-label=" "` is functionally
189223 // empty for assistive tech and should not count as an accessible name.
190224 {
@@ -222,6 +256,22 @@ ruleTester.run('template-anchor-has-content', rule, {
222256 output : null ,
223257 errors : [ { messageId : 'anchorHasContent' } ] ,
224258 } ,
259+ // aria-hidden={{"false"}} — string-literal mustache resolves to "false";
260+ // anchor is NOT hidden, so the content check applies and should flag.
261+ {
262+ filename : 'test.gjs' ,
263+ code : '<template><a href="/x" aria-hidden={{"false"}} /></template>' ,
264+ output : null ,
265+ errors : [ { messageId : 'anchorHasContent' } ] ,
266+ } ,
267+ // aria-hidden={{"true"}} on a child — child is hidden, contributes nothing;
268+ // the anchor itself is still in scope and has no accessible content.
269+ {
270+ filename : 'test.gjs' ,
271+ code : '<template><a href="/x"><span aria-hidden={{"true"}}>X</span></a></template>' ,
272+ output : null ,
273+ errors : [ { messageId : 'anchorHasContent' } ] ,
274+ } ,
225275 ] ,
226276} ) ;
227277
0 commit comments