Skip to content

Commit b8a4444

Browse files
Merge pull request #2705 from johanrd/test/coverage-upstream-gaps
Post-merge-review: add missing tests from ember-template-lint test suite
2 parents ce2b6ee + 387bbf9 commit b8a4444

14 files changed

Lines changed: 439 additions & 0 deletions

tests/lib/rules/template-link-rel-noopener.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ ruleTester.run('template-link-rel-noopener', rule, {
1414
'<template><a href="/" target="_blank" rel="nofollow noreferrer noopener">Link</a></template>',
1515
// no target="_blank" means no rel required
1616
'<template><a href="/">Link</a></template>',
17+
// target="_self" does not require rel
18+
'<template><a href="/some/where" target="_self"></a></template>',
1719
],
1820
invalid: [
1921
// no rel attribute at all
@@ -43,3 +45,43 @@ ruleTester.run('template-link-rel-noopener', rule, {
4345
},
4446
],
4547
});
48+
49+
const hbsRuleTester = new RuleTester({
50+
parser: require.resolve('ember-eslint-parser/hbs'),
51+
parserOptions: {
52+
ecmaVersion: 2022,
53+
sourceType: 'module',
54+
},
55+
});
56+
57+
hbsRuleTester.run('template-link-rel-noopener (hbs)', rule, {
58+
valid: [
59+
'<a href="/some/where"></a>',
60+
'<a href="/some/where" target="_self"></a>',
61+
'<a href="/some/where" target="_blank" rel="noopener noreferrer"></a>',
62+
'<a href="/some/where" target="_blank" rel="noreferrer noopener"></a>',
63+
'<a href="/some/where" target="_blank" rel="nofollow noreferrer noopener"></a>',
64+
],
65+
invalid: [
66+
{
67+
code: '<a href="/some/where" target="_blank"></a>',
68+
output: '<a href="/some/where" target="_blank" rel="noopener noreferrer"></a>',
69+
errors: [{ messageId: 'missingRel' }],
70+
},
71+
{
72+
code: '<a href="/some/where" target="_blank" rel="nofollow"></a>',
73+
output: '<a href="/some/where" target="_blank" rel="nofollow noopener noreferrer"></a>',
74+
errors: [{ messageId: 'missingRel' }],
75+
},
76+
{
77+
code: '<a href="/some/where" target="_blank" rel="noopener"></a>',
78+
output: '<a href="/some/where" target="_blank" rel="noopener noreferrer"></a>',
79+
errors: [{ messageId: 'missingRel' }],
80+
},
81+
{
82+
code: '<a href="/some/where" target="_blank" rel="noreferrer"></a>',
83+
output: '<a href="/some/where" target="_blank" rel="noopener noreferrer"></a>',
84+
errors: [{ messageId: 'missingRel' }],
85+
},
86+
],
87+
});

tests/lib/rules/template-no-abstract-roles.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,55 @@ ruleTester.run('template-no-abstract-roles', rule, {
1919
output: null,
2020
errors: [{ messageId: 'abstractRole' }],
2121
},
22+
{
23+
code: '<template><div role="composite"></div></template>',
24+
output: null,
25+
errors: [{ messageId: 'abstractRole' }],
26+
},
27+
{
28+
code: '<template><input role="input" /></template>',
29+
output: null,
30+
errors: [{ messageId: 'abstractRole' }],
31+
},
32+
{
33+
code: '<template><div role="landmark"></div></template>',
34+
output: null,
35+
errors: [{ messageId: 'abstractRole' }],
36+
},
37+
{
38+
code: '<template><input role="range" /></template>',
39+
output: null,
40+
errors: [{ messageId: 'abstractRole' }],
41+
},
42+
{
43+
code: '<template><div role="roletype"></div></template>',
44+
output: null,
45+
errors: [{ messageId: 'abstractRole' }],
46+
},
47+
{
48+
code: '<template><div role="section"></div></template>',
49+
output: null,
50+
errors: [{ messageId: 'abstractRole' }],
51+
},
52+
{
53+
code: '<template><div role="sectionhead"></div></template>',
54+
output: null,
55+
errors: [{ messageId: 'abstractRole' }],
56+
},
57+
{
58+
code: '<template><select role="select"></select></template>',
59+
output: null,
60+
errors: [{ messageId: 'abstractRole' }],
61+
},
62+
{
63+
code: '<template><div role="structure"></div></template>',
64+
output: null,
65+
errors: [{ messageId: 'abstractRole' }],
66+
},
67+
{
68+
code: '<template><div role="window"></div></template>',
69+
output: null,
70+
errors: [{ messageId: 'abstractRole' }],
71+
},
2272
],
2373
});

tests/lib/rules/template-no-accesskey-attribute.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,23 @@ ruleTester.run('template-no-accesskey-attribute', rule, {
5353
},
5454
],
5555
},
56+
// Boolean attribute (no value)
57+
{
58+
code: '<template><button accesskey></button></template>',
59+
output: '<template><button></button></template>',
60+
errors: [{ messageId: 'noAccesskey' }],
61+
},
62+
// Dynamic mustache value
63+
{
64+
code: '<template><button accesskey={{someKey}}></button></template>',
65+
output: '<template><button></button></template>',
66+
errors: [{ messageId: 'noAccesskey' }],
67+
},
68+
// Concat string attribute
69+
{
70+
code: '<template><button accesskey="{{someKey}}"></button></template>',
71+
output: '<template><button></button></template>',
72+
errors: [{ messageId: 'noAccesskey' }],
73+
},
5674
],
5775
});

tests/lib/rules/template-no-aria-hidden-body.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,10 @@ ruleTester.run('template-no-aria-hidden-body', rule, {
1717
output: '<template><body></body></template>',
1818
errors: [{ messageId: 'noAriaHiddenBody' }],
1919
},
20+
{
21+
code: '<template><body aria-hidden></body></template>',
22+
output: '<template><body></body></template>',
23+
errors: [{ messageId: 'noAriaHiddenBody' }],
24+
},
2025
],
2126
});

tests/lib/rules/template-no-array-prototype-extensions.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ ruleTester.run('template-no-array-prototype-extensions', rule, {
1414
'<template>{{@items}}</template>',
1515
'<template>{{firstObject}}</template>',
1616
'<template>{{length}}</template>',
17+
// get helper with firstObject/lastObject as a direct top-level property (not an extension)
18+
"<template>{{get this 'firstObject'}}</template>",
19+
"<template>{{get this 'lastObject.name'}}</template>",
20+
// Plain text nodes are not flagged
21+
'<template>Just a regular text in the template bar.firstObject bar.lastObject.foo</template>',
22+
// String-literal HTML attributes are not flagged
23+
'<template><Foo foo="bar.firstObject.baz" /></template>',
1724
],
1825

1926
invalid: [
@@ -84,5 +91,29 @@ ruleTester.run('template-no-array-prototype-extensions', rule, {
8491
output: null,
8592
errors: [{ messageId: 'lastObject' }],
8693
},
94+
// lastObject — in get helper string literal, no fix
95+
{
96+
code: "<template><Foo @bar={{get this 'list.lastObject'}} /></template>",
97+
output: null,
98+
errors: [{ messageId: 'lastObject' }],
99+
},
100+
// firstObject — get helper with `this` as object and string literal path
101+
{
102+
code: "<template><Foo @bar={{get this 'list.firstObject'}} /></template>",
103+
output: "<template><Foo @bar={{get this 'list.0'}} /></template>",
104+
errors: [{ messageId: 'firstObject' }],
105+
},
106+
// firstObject — get helper with @arg as object and firstObject at start of string path
107+
{
108+
code: "<template><Foo @bar={{get @list 'firstObject.name'}} /></template>",
109+
output: "<template><Foo @bar={{get @list '0.name'}} /></template>",
110+
errors: [{ messageId: 'firstObject' }],
111+
},
112+
// lastObject — in named hash argument
113+
{
114+
code: '<template>{{foo [email protected]}}</template>',
115+
output: null,
116+
errors: [{ messageId: 'lastObject' }],
117+
},
87118
],
88119
});

tests/lib/rules/template-no-curly-component-invocation.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,11 @@ hbsRuleTester.run('template-no-curly-component-invocation', rule, {
277277
code: '{{#each items as |disallowed|}}{{disallowed}}{{/each}}',
278278
options: [{ disallow: ['disallowed'], noImplicitThis: false }],
279279
},
280+
// requireDash: true — single-word names with named args are not flagged (not obviously a component)
281+
{
282+
code: '{{foo bar=baz}}',
283+
options: [{ requireDash: true }],
284+
},
280285
],
281286
invalid: [
282287
{

tests/lib/rules/template-no-duplicate-landmark-elements.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ ruleTester.run('template-no-duplicate-landmark-elements', rule, {
2020
"<template><main><header><h1>Main Page Header</h1></header></main><dialog id='my-dialog'><header><h1>Dialog Header</h1></header></dialog></template>",
2121
// Landmarks inside dialog are in a separate scope
2222
'<template><nav></nav><dialog><nav></nav></dialog></template>',
23+
// Landmarks inside popover element are in a separate scope
24+
'<template><nav></nav><div popover><nav></nav></div></template>',
2325
// Dynamic role values — can't determine role statically
2426
'<template><div role={{this.role}}></div><div role={{this.role}}></div></template>',
2527
// Dynamic aria-label on one landmark — can't infer whether it duplicates a sibling

tests/lib/rules/template-no-element-event-actions.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ ruleTester.run('template-no-element-event-actions', rule, {
3232
`,
3333
output: null,
3434
},
35+
// requireActionHelper: true — non-action mustache is not flagged
36+
{
37+
filename: 'my-component.gjs',
38+
code: '<template><button type="button" onclick={{this.myAction}}></button></template>',
39+
output: null,
40+
options: [{ requireActionHelper: true }],
41+
},
42+
// requireActionHelper: false — string event handler is not flagged
43+
{
44+
filename: 'my-component.gjs',
45+
code: '<template><button type="button" onclick="myFunction()"></button></template>',
46+
output: null,
47+
options: [{ requireActionHelper: false }],
48+
},
3549
],
3650

3751
invalid: [
@@ -69,5 +83,69 @@ ruleTester.run('template-no-element-event-actions', rule, {
6983
},
7084
],
7185
},
86+
// requireActionHelper: false — any mustache on event attribute is flagged
87+
{
88+
filename: 'my-component.gjs',
89+
code: '<template><button type="button" onclick={{this.myAction}}></button></template>',
90+
output: null,
91+
options: [{ requireActionHelper: false }],
92+
errors: [{ messageId: 'noElementEventActions' }],
93+
},
94+
// requireActionHelper: true — only {{action ...}} mustaches are flagged
95+
{
96+
filename: 'my-component.gjs',
97+
code: '<template><button onclick={{action "myAction"}}></button></template>',
98+
output: null,
99+
options: [{ requireActionHelper: true }],
100+
errors: [{ messageId: 'noElementEventActions' }],
101+
},
102+
],
103+
});
104+
105+
const hbsRuleTester = new RuleTester({
106+
parser: require.resolve('ember-eslint-parser/hbs'),
107+
parserOptions: {
108+
ecmaVersion: 2022,
109+
sourceType: 'module',
110+
},
111+
});
112+
113+
hbsRuleTester.run('template-no-element-event-actions (hbs)', rule, {
114+
valid: [
115+
'<button></button>',
116+
'<button type="button" onclick="myFunction()"></button>',
117+
'<button type="button" {{on "click" this.handleClick}}></button>',
118+
{
119+
code: '<button type="button" onclick={{this.myAction}}></button>',
120+
options: [{ requireActionHelper: true }],
121+
},
122+
{
123+
code: '<button type="button" onclick="myFunction()"></button>',
124+
options: [{ requireActionHelper: false }],
125+
},
126+
],
127+
invalid: [
128+
{
129+
code: '<button onclick={{action "myAction"}}></button>',
130+
output: null,
131+
errors: [{ messageId: 'noElementEventActions' }],
132+
},
133+
{
134+
code: '<button type="button" onclick={{this.myAction}}></button>',
135+
output: null,
136+
errors: [{ messageId: 'noElementEventActions' }],
137+
},
138+
{
139+
code: '<button type="button" onclick={{this.myAction}}></button>',
140+
output: null,
141+
options: [{ requireActionHelper: false }],
142+
errors: [{ messageId: 'noElementEventActions' }],
143+
},
144+
{
145+
code: '<button onclick={{action "myAction"}}></button>',
146+
output: null,
147+
options: [{ requireActionHelper: true }],
148+
errors: [{ messageId: 'noElementEventActions' }],
149+
},
72150
],
73151
});

tests/lib/rules/template-no-invalid-interactive.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,30 @@ ruleTester.run('template-no-invalid-interactive', rule, {
8282
'<template><@someComponent onclick={{this.click}} /></template>',
8383
'<template><this.myComponent onclick={{this.click}} /></template>',
8484
'<template><ns.SomeWidget onclick={{this.click}} /></template>',
85+
86+
// additionalInteractiveTags: tags listed are treated as interactive
87+
{
88+
code: '<template><div {{on "click" this.onClick}}></div></template>',
89+
options: [{ additionalInteractiveTags: ['div'] }],
90+
},
91+
{
92+
code: '<template><div {{action "foo"}}></div></template>',
93+
options: [{ additionalInteractiveTags: ['div'] }],
94+
},
95+
{
96+
code: '<template><div onclick={{action "foo"}}></div></template>',
97+
options: [{ additionalInteractiveTags: ['div'] }],
98+
},
99+
100+
// ignoredTags: tags listed are skipped entirely
101+
{
102+
code: '<template><div {{on "click" this.actionName}}>...</div></template>',
103+
options: [{ ignoredTags: ['div'] }],
104+
},
105+
{
106+
code: '<template><div onclick={{action "foo"}}></div></template>',
107+
options: [{ ignoredTags: ['div'] }],
108+
},
85109
],
86110

87111
invalid: [

tests/lib/rules/template-no-invalid-role.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ ruleTester.run('template-no-invalid-role', rule, {
6666
'<template><div role="Button">Click</div></template>',
6767
'<template><div role="NAVIGATION">Nav</div></template>',
6868
'<template><div role="ALERT">Alert</div></template>',
69+
// catchNonexistentRoles: false — non-existent roles are not flagged
70+
{
71+
code: '<template><div role="command interface"></div></template>',
72+
options: [{ catchNonexistentRoles: false }],
73+
},
6974
],
7075

7176
invalid: [
@@ -166,6 +171,12 @@ ruleTester.run('template-no-invalid-role', rule, {
166171
output: null,
167172
errors: [{ message: "Invalid ARIA role 'COMMAND INTERFACE'. Must be a valid ARIA role." }],
168173
},
174+
{
175+
code: '<template><div role="command interface"></div></template>',
176+
output: null,
177+
options: [{ catchNonexistentRoles: true }],
178+
errors: [{ message: "Invalid ARIA role 'command interface'. Must be a valid ARIA role." }],
179+
},
169180

170181
// Newly added SEMANTIC_ELEMENTS: presentation/none on iframe, video, audio
171182
{

0 commit comments

Comments
 (0)