Skip to content

Commit d86ed04

Browse files
committed
test: add missing test coverage across 14 ported rules
1 parent 485bb7f commit d86ed04

12 files changed

Lines changed: 415 additions & 0 deletions

tests/lib/rules/template-attribute-order.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ ruleTester.run('template-attribute-order', rule, {
1818
'<template><div aria-label="x" aria-hidden="true"></div></template>',
1919
// Correct full order
2020
'<template><input class="x" id="y" role="r" aria-label="l" data-test-foo="1" type="text" name="n" value="v" placeholder="p" disabled></template>',
21+
// Custom order config: id before class is valid when order is ['id', 'class']
22+
{
23+
code: '<template><div id="bar" class="foo"></div></template>',
24+
options: [{ order: ['id', 'class'] }],
25+
},
2126
],
2227

2328
invalid: [
@@ -60,5 +65,19 @@ ruleTester.run('template-attribute-order', rule, {
6065
output: '<template><div\n class="y"\n name="x"\n></div></template>',
6166
errors: [{ messageId: 'wrongOrder' }],
6267
},
68+
// Custom order: id before class
69+
{
70+
code: '<template><div class="foo" id="bar"></div></template>',
71+
output: '<template><div id="bar" class="foo"></div></template>',
72+
options: [{ order: ['id', 'class'] }],
73+
errors: [{ messageId: 'wrongOrder' }],
74+
},
75+
// Custom order: role, class
76+
{
77+
code: '<template><div class="btn" role="button"></div></template>',
78+
output: '<template><div role="button" class="btn"></div></template>',
79+
options: [{ order: ['role', 'class'] }],
80+
errors: [{ messageId: 'wrongOrder' }],
81+
},
6382
],
6483
});

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-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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ 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>",
1720
],
1821

1922
invalid: [
@@ -84,5 +87,23 @@ ruleTester.run('template-no-array-prototype-extensions', rule, {
8487
output: null,
8588
errors: [{ messageId: 'lastObject' }],
8689
},
90+
// lastObject — in get helper string literal, no fix
91+
{
92+
code: "<template><Foo @bar={{get this 'list.lastObject'}} /></template>",
93+
output: null,
94+
errors: [{ messageId: 'lastObject' }],
95+
},
96+
// firstObject — get helper with `this` as object and string literal path
97+
{
98+
code: "<template><Foo @bar={{get this 'list.firstObject'}} /></template>",
99+
output: "<template><Foo @bar={{get this 'list.0'}} /></template>",
100+
errors: [{ messageId: 'firstObject' }],
101+
},
102+
// firstObject — get helper with @arg as object and firstObject at start of string path
103+
{
104+
code: "<template><Foo @bar={{get @list 'firstObject.name'}} /></template>",
105+
output: "<template><Foo @bar={{get @list '0.name'}} /></template>",
106+
errors: [{ messageId: 'firstObject' }],
107+
},
87108
],
88109
});

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+
options: [{ requireActionHelper: true }],
40+
output: null,
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+
options: [{ requireActionHelper: false }],
47+
output: null,
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+
options: [{ requireActionHelper: false }],
91+
output: null,
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+
options: [{ requireActionHelper: true }],
99+
output: null,
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+
options: [{ requireActionHelper: false }],
141+
output: null,
142+
errors: [{ messageId: 'noElementEventActions' }],
143+
},
144+
{
145+
code: '<button onclick={{action "myAction"}}></button>',
146+
options: [{ requireActionHelper: true }],
147+
output: null,
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
{

tests/lib/rules/template-no-obsolete-elements.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,46 @@ ruleTester.run('template-no-obsolete-elements', rule, {
2121
output: null,
2222
errors: [{ messageId: 'obsolete' }],
2323
},
24+
{
25+
code: '<template><big></big></template>',
26+
output: null,
27+
errors: [{ messageId: 'obsolete' }],
28+
},
29+
{
30+
code: '<template><blink></blink></template>',
31+
output: null,
32+
errors: [{ messageId: 'obsolete' }],
33+
},
34+
{
35+
code: '<template><center></center></template>',
36+
output: null,
37+
errors: [{ messageId: 'obsolete' }],
38+
},
39+
{
40+
code: '<template><font></font></template>',
41+
output: null,
42+
errors: [{ messageId: 'obsolete' }],
43+
},
44+
{
45+
code: '<template><frame></frame></template>',
46+
output: null,
47+
errors: [{ messageId: 'obsolete' }],
48+
},
49+
{
50+
code: '<template><frameset></frameset></template>',
51+
output: null,
52+
errors: [{ messageId: 'obsolete' }],
53+
},
54+
{
55+
code: '<template><strike></strike></template>',
56+
output: null,
57+
errors: [{ messageId: 'obsolete' }],
58+
},
59+
{
60+
code: '<template><tt></tt></template>',
61+
output: null,
62+
errors: [{ messageId: 'obsolete' }],
63+
},
2464
// Element's own block params must not shadow its own tag name.
2565
{
2666
code: '<template><marquee as |marquee|></marquee></template>',

0 commit comments

Comments
 (0)