Skip to content

Commit ec2952c

Browse files
authoredDec 17, 2023
feat(new rule): setup prefer expect assertions (#326)
* feat(new rule): setup prefer expect assertions * chore(vitest arning): remove visted module warning
1 parent 37261f0 commit ec2952c

12 files changed

+1016
-367
lines changed
 

‎.eslintrc.json

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
],
1010
"no-case-declarations": 0,
1111
"no-tabs": "off",
12+
"no-mixed-spaces-and-tabs": "off",
1213
"vitest/unbound-method": "off"
1314
}
1415
}

‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ export default [
152152
| [prefer-comparison-matcher](docs/rules/prefer-comparison-matcher.md) | Suggest using the built-in comparison matchers | | 🌐 | 🔧 | |
153153
| [prefer-each](docs/rules/prefer-each.md) | Prefer `each` rather than manual loops | | 🌐 | | |
154154
| [prefer-equality-matcher](docs/rules/prefer-equality-matcher.md) | Suggest using the built-in quality matchers | | 🌐 | | 💡 |
155+
| [prefer-expect-assertions](docs/rules/prefer-expect-assertions.md) | Suggest using expect assertions instead of callbacks | | 🌐 | | 💡 |
155156
| [prefer-expect-resolves](docs/rules/prefer-expect-resolves.md) | Suggest using `expect().resolves` over `expect(await ...)` syntax | | 🌐 | 🔧 | |
156157
| [prefer-hooks-in-order](docs/rules/prefer-hooks-in-order.md) | Prefer having hooks in consistent order | | 🌐 | | |
157158
| [prefer-hooks-on-top](docs/rules/prefer-hooks-on-top.md) | Suggest having hooks before any test cases | | 🌐 | | |
+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Suggest using expect assertions instead of callbacks (`vitest/prefer-expect-assertions`)
2+
3+
⚠️ This rule _warns_ in the 🌐 `all` config.
4+
5+
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
6+
7+
<!-- end auto-generated rule header -->
8+
9+
Ensure every test to have either `expect.assertions(<number of assertions>)` OR
10+
`expect.hasAssertions()` as its first expression.
11+
12+
This will warn if a test has no assertions, or if it has assertions but they are not the first expression.
13+
14+
## Examples
15+
16+
Examples of **incorrect** code for this rule:
17+
18+
```js
19+
test('no assertions', () => {
20+
// ...
21+
});
22+
23+
test('assertions not first', () => {
24+
expect(true).toBe(true);
25+
// ...
26+
});
27+
```
28+
29+
Examples of **correct** code for this rule:
30+
31+
```js
32+
test('assertions first', () => {
33+
expect.assertions(1);
34+
// ...
35+
});
36+
37+
test('assertions first', () => {
38+
expect.hasAssertions();
39+
// ...
40+
});
41+
```
42+
43+
## Options
44+
45+
`onlyFunctionsWithAsyncKeyword` (default: `false`)
46+
47+
When `true`, only functions with the `async` keyword will be checked.
48+
49+
when this option is enabled the following code will be considered incorrect:
50+
51+
```js
52+
test('assertions first', () => {
53+
const data = await fetchData();
54+
expect(data).toBe('peanut butter');
55+
});
56+
```
57+
58+
To fix this, you'll need to add `expect.assertions(1)` or `expect.hasAssertions()` as the first expression:
59+
60+
```js
61+
test('assertions first', () => {
62+
expect.assertions(1);
63+
const data = await fetchData();
64+
expect(data).toBe('peanut butter');
65+
});
66+
```
67+
68+
`onlyFunctionsWithExpectInLoop` (default: `false`)
69+
70+
When `true`, only functions with `expect` in a loop will be checked.
71+
72+
when this option is enabled the following code will be considered incorrect:
73+
74+
```js
75+
test('assertions first', () => {
76+
for (let i = 0; i < 10; i++) {
77+
expect(i).toBeLessThan(10);
78+
}
79+
});
80+
```
81+
82+
To fix this, you'll need to add `expect.assertions(1)` or `expect.hasAssertions()` as the first expression:
83+
84+
```js
85+
test('assertions first', () => {
86+
expect.hasAssertions();
87+
for (let i = 0; i < 10; i++) {
88+
expect(i).toBeLessThan(10);
89+
}
90+
});
91+
```
92+
93+
`onlyFunctionsWithExpectInCallback`
94+
95+
When `true`, only functions with `expect` in a callback will be checked.
96+
97+
when this option is enabled the following code will be considered incorrect:
98+
99+
```js
100+
test('assertions first', () => {
101+
fetchData((data) => {
102+
expect(data).toBe('peanut butter');
103+
});
104+
});
105+
```
106+
107+
To fix this, you'll need to add `expect.assertions(1)` or `expect.hasAssertions()` as the first expression:
108+
109+
```js
110+
test('assertions first', () => {
111+
expect.assertions(1);
112+
fetchData((data) => {
113+
expect(data).toBe('peanut butter');
114+
});
115+
});
116+
```

‎fixtures/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
"license": "MIT",
1313
"dependencies": {
1414
"eslint-plugin-vitest": "link:../",
15-
"vitest": "^1.0.2"
15+
"vitest": "^1.0.4"
1616
},
1717
"devDependencies": {
18-
"eslint": "^8.55.0"
18+
"eslint": "^8.56.0"
1919
}
2020
}

‎package.json

+9-9
Original file line numberDiff line numberDiff line change
@@ -40,26 +40,26 @@
4040
"tsc": "tsc --noEmit"
4141
},
4242
"devDependencies": {
43-
"@babel/types": "^7.23.5",
43+
"@babel/types": "^7.23.6",
4444
"@types/mocha": "^10.0.6",
4545
"@types/node": "^20.10.4",
46-
"@typescript-eslint/eslint-plugin": "^6.13.2",
47-
"@typescript-eslint/rule-tester": "^6.13.2",
46+
"@typescript-eslint/eslint-plugin": "^6.14.0",
47+
"@typescript-eslint/rule-tester": "^6.14.0",
4848
"@veritem/eslint-config": "^0.0.11",
49-
"bumpp": "^9.2.0",
49+
"bumpp": "^9.2.1",
5050
"concurrently": "^8.2.2",
51-
"eslint": "^8.55.0",
51+
"eslint": "^8.56.0",
5252
"eslint-doc-generator": "^1.6.1",
53-
"eslint-plugin-eslint-plugin": "^5.1.1",
53+
"eslint-plugin-eslint-plugin": "^5.2.1",
5454
"eslint-plugin-node": "^11.1.0",
55-
"eslint-plugin-vitest": "^0.3.10",
55+
"eslint-plugin-vitest": "^0.3.17",
5656
"eslint-remote-tester": "^3.0.1",
5757
"eslint-remote-tester-repositories": "^1.0.1",
5858
"ts-node": "^10.9.2",
5959
"tsx": "^4.6.2",
6060
"typescript": "^5.3.3",
6161
"unbuild": "^2.0.0",
62-
"vitest": "^1.0.2"
62+
"vitest": "^1.0.4"
6363
},
6464
"engines": {
6565
"node": "^18.0.0 || >= 20.0.0"
@@ -77,6 +77,6 @@
7777
}
7878
},
7979
"dependencies": {
80-
"@typescript-eslint/utils": "^6.13.2"
80+
"@typescript-eslint/utils": "^6.14.0"
8181
}
8282
}

‎pnpm-lock.yaml

+238-246
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎scripts/chain-permutations.mjs

-108
This file was deleted.

‎scripts/chain-permutations.ts

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// imported from https://github.com/veritem/eslint-plugin-vitest/pull/293
2+
// This script generates all possible permutations for vitest methods
3+
import { per } from 'percom'
4+
5+
const data = [
6+
{
7+
names: ['beforeEach', 'beforeAll', 'afterEach', 'afterAll'],
8+
first: [],
9+
conditions: [],
10+
methods: [],
11+
last: []
12+
},
13+
{
14+
names: ['it', 'test'],
15+
first: ['extend'],
16+
conditions: ['skipIf', 'runIf'],
17+
methods: ['skip', 'only', 'concurrent', 'sequential', 'todo', 'fails'],
18+
last: ['each']
19+
},
20+
{
21+
names: ['bench'],
22+
first: [],
23+
conditions: ['skipIf', 'runIf'],
24+
methods: ['skip', 'only', 'todo'],
25+
last: []
26+
},
27+
{
28+
names: ['describe'],
29+
first: [],
30+
conditions: ['skipIf', 'runIf'],
31+
methods: ['skip', 'only', 'concurrent', 'sequential', 'shuffle', 'todo'],
32+
last: ['each']
33+
}
34+
]
35+
36+
const DEPTH = 3
37+
38+
const allPermutations: string[] = []
39+
40+
const depths = (maxDepth) => Array.from({ length: maxDepth }, (_, i) => i)
41+
42+
data.forEach((q) => {
43+
q.names.forEach((name) => {
44+
allPermutations.push(name)
45+
46+
const maxDepth = Math.min(DEPTH, q.methods.length)
47+
const methodPerms = depths(maxDepth).flatMap((i) => [
48+
...per(q.methods, i + 1),
49+
...q.first.flatMap((first) =>
50+
(per(q.methods, i) || ['']).map((p) => [first, ...p])
51+
),
52+
...q.conditions.flatMap((condition) =>
53+
(per(q.methods, i) || ['']).map((p) => [condition, ...p])
54+
),
55+
...q.last.flatMap((last) =>
56+
(per(q.methods, i) || ['']).map((p) => [...p, last])
57+
),
58+
...(i > 0
59+
? q.first.flatMap((first) =>
60+
q.conditions.flatMap((condition) =>
61+
(per(q.methods, i - 1) || ['']).map((p) => [
62+
first,
63+
condition,
64+
...p
65+
])
66+
)
67+
)
68+
: []),
69+
...(i > 0
70+
? q.first.flatMap((first) =>
71+
q.last.flatMap((last) =>
72+
(per(q.methods, i - 1) || ['']).map((p) => [first, ...p, last])
73+
)
74+
)
75+
: []),
76+
...(i > 0
77+
? q.conditions.flatMap((condition) =>
78+
q.last.flatMap((last) =>
79+
(per(q.methods, i - 1) || ['']).map((p) => [
80+
condition,
81+
...p,
82+
last
83+
])
84+
)
85+
)
86+
: []),
87+
...(i > 1
88+
? q.first.flatMap((first) =>
89+
q.conditions.flatMap((condition) =>
90+
q.last.flatMap((last) =>
91+
(per(q.methods, i - 2) || ['']).map((p) => [
92+
first,
93+
condition,
94+
...p,
95+
last
96+
])
97+
)
98+
)
99+
)
100+
: [])
101+
])
102+
const allPerms = methodPerms.map((p) => [name, ...p].join('.'))
103+
allPermutations.push(...allPerms)
104+
})
105+
})
106+
107+
console.log(allPermutations)

‎src/index.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import preferSpyOn, { RULE_NAME as preferSpyOnName } from './rules/prefer-spy-on
5050
import preferComparisonMatcher, { RULE_NAME as preferComparisonMatcherName } from './rules/prefer-comparison-matcher'
5151
import preferToContain, { RULE_NAME as preferToContainName } from './rules/prefer-to-contain'
5252
import preferCalledExactlyOnceWith, { RULE_NAME as preferCalledExactlyOnceWithName } from './rules/prefer-called-exactly-once-with'
53+
import preferExpectAssertions, {RULE_NAME as preferExpectAssertionsName} from './rules/prefer-expect-assertions'
5354
// import unboundMethod, { RULE_NAME as unboundMethodName } from './rules/unbound-method'
5455

5556
const createConfig = (rules: Record<string, string>) => ({
@@ -105,7 +106,8 @@ const allRules = {
105106
[preferSpyOnName]: 'warn',
106107
[preferComparisonMatcherName]: 'warn',
107108
[preferToContainName]: 'warn',
108-
[preferCalledExactlyOnceWithName]: 'warn'
109+
[preferCalledExactlyOnceWithName]: 'warn',
110+
[preferExpectAssertionsName]: 'warn'
109111
// [unboundMethodName]: 'warn'
110112
}
111113

@@ -174,7 +176,8 @@ export default {
174176
[preferSpyOnName]: preferSpyOn,
175177
[preferComparisonMatcherName]: preferComparisonMatcher,
176178
[preferToContainName]: preferToContain,
177-
[preferCalledExactlyOnceWithName]: preferCalledExactlyOnceWith
179+
[preferCalledExactlyOnceWithName]: preferCalledExactlyOnceWith,
180+
[preferExpectAssertionsName]: preferExpectAssertions
178181
// [unboundMethodName]: unboundMethod
179182
},
180183
configs: {

‎src/rules/prefer-expect-assertions.ts

+239
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
import { AST_NODE_TYPES, type TSESLint, type TSESTree } from '@typescript-eslint/utils'
2+
import { createEslintRule, getAccessorValue, isFunction, removeExtraArgumentsFixer } from '../utils'
3+
import { ParsedExpectVitestFnCall, isTypeOfVitestFnCall, parseVitestFnCall } from '../utils/parseVitestFnCall'
4+
5+
type Options = {
6+
onlyFunctionsWithAsyncKeyword?: boolean;
7+
onlyFunctionsWithExpectInLoop?: boolean;
8+
onlyFunctionsWithExpectInCallback?: boolean
9+
}
10+
11+
export const RULE_NAME = 'prefer-expect-assertions'
12+
13+
type MessageIds =
14+
| 'hasAssertionsTakesNoArguments'
15+
| 'assertionsRequiresOneArgument'
16+
| 'assertionsRequiresNumberArgument'
17+
| 'haveExpectAssertions'
18+
| 'suggestAddingHasAssertions'
19+
| 'suggestAddingAssertions'
20+
| 'suggestRemovingExtraArguments';
21+
22+
const isFirstStatement = (node: TSESTree.CallExpression): boolean => {
23+
let parent: TSESTree.Node['parent'] = node
24+
25+
while (parent) {
26+
if (parent.parent?.type === AST_NODE_TYPES.BlockStatement)
27+
return parent.parent.body[0] === parent
28+
29+
if (parent.parent?.type === AST_NODE_TYPES.ArrowFunctionExpression)
30+
return true
31+
32+
parent = parent.parent
33+
}
34+
35+
throw new Error('Could not find parent block statement')
36+
}
37+
38+
const suggestRemovingExtraArguments = (context: TSESLint.RuleContext<string, unknown[]>,
39+
func: TSESTree.CallExpression,
40+
from: number): TSESLint.ReportSuggestionArray<MessageIds>[0] => ({
41+
messageId: 'suggestRemovingExtraArguments',
42+
fix: fixer => removeExtraArgumentsFixer(fixer, context, func, from)
43+
})
44+
45+
export default createEslintRule<Options[], MessageIds>({
46+
name: 'prefer-expect-assertions',
47+
meta: {
48+
docs: {
49+
description: 'Suggest using expect assertions instead of callbacks',
50+
recommended: 'error'
51+
},
52+
messages: {
53+
hasAssertionsTakesNoArguments:
54+
'`expect.hasAssertions` expects no arguments',
55+
assertionsRequiresOneArgument:
56+
'`expect.assertions` excepts a single argument of type number',
57+
assertionsRequiresNumberArgument: 'This argument should be a number',
58+
haveExpectAssertions:
59+
'Every test should have either `expect.assertions(<number of assertions>)` or `expect.hasAssertions()` as its first expression',
60+
suggestAddingHasAssertions: 'Add `expect.hasAssertions()`',
61+
suggestAddingAssertions:
62+
'Add `expect.assertions(<number of assertions>)`',
63+
suggestRemovingExtraArguments: 'Remove extra arguments'
64+
},
65+
type: 'suggestion',
66+
hasSuggestions: true,
67+
schema: [
68+
{
69+
type: 'object',
70+
properties: {
71+
onlyFunctionsWithAsyncKeyword: { type: 'boolean' },
72+
onlyFunctionsWithExpectInLoop: { type: 'boolean' },
73+
onlyFunctionsWithExpectInCallback: { type: 'boolean' }
74+
},
75+
additionalProperties: false
76+
}
77+
]
78+
},
79+
defaultOptions: [
80+
{
81+
onlyFunctionsWithAsyncKeyword: false,
82+
onlyFunctionsWithExpectInCallback: false,
83+
onlyFunctionsWithExpectInLoop: false
84+
}
85+
],
86+
create(context, [options]) {
87+
let expressionDepth = 0
88+
let hasExpectInCallBack = false
89+
let hasExpectInLoop = false
90+
let hasExpectAssertAsFirstStatement = false
91+
let inTestCaseCall = false
92+
let inForLoop = false
93+
94+
const shouldCheckFunction = (testFunction: TSESTree.FunctionLike) => {
95+
if (!options.onlyFunctionsWithAsyncKeyword && !options.onlyFunctionsWithExpectInCallback && !options.onlyFunctionsWithExpectInLoop)
96+
return true
97+
98+
if (options.onlyFunctionsWithAsyncKeyword) {
99+
if (testFunction.async)
100+
return true
101+
}
102+
103+
if (options.onlyFunctionsWithExpectInCallback) {
104+
if (hasExpectInCallBack)
105+
return true
106+
}
107+
108+
if (options.onlyFunctionsWithExpectInLoop) {
109+
if (hasExpectInLoop)
110+
return true
111+
}
112+
113+
return false
114+
}
115+
116+
function checkExpectHasAssertions(expectFnCall: ParsedExpectVitestFnCall, func: TSESTree.CallExpression) {
117+
if (getAccessorValue(expectFnCall.members[0]) === 'hasAssertions') {
118+
if (expectFnCall.args.length) {
119+
context.report({
120+
messageId: 'hasAssertionsTakesNoArguments',
121+
node: expectFnCall.matcher,
122+
suggest: [suggestRemovingExtraArguments(context, func, 0)]
123+
})
124+
}
125+
return
126+
}
127+
128+
if (expectFnCall.args.length !== 1) {
129+
let { loc } = expectFnCall.matcher
130+
const suggestions: TSESLint.ReportSuggestionArray<MessageIds> = []
131+
132+
if (expectFnCall.args.length) {
133+
loc = expectFnCall.args[1].loc
134+
suggestions.push(suggestRemovingExtraArguments(context, func, 1))
135+
}
136+
137+
context.report({
138+
messageId: 'assertionsRequiresOneArgument',
139+
suggest: suggestions,
140+
loc
141+
})
142+
return
143+
}
144+
145+
const [arg] = expectFnCall.args
146+
147+
if (arg.type === AST_NODE_TYPES.Literal && typeof arg.value === 'number' && Number.isInteger(arg.value))
148+
return
149+
150+
context.report({
151+
messageId: 'assertionsRequiresNumberArgument',
152+
node: arg
153+
})
154+
}
155+
const enterExpression = () => (inTestCaseCall && expressionDepth++)
156+
const exitExpression = () => (inTestCaseCall && expressionDepth--)
157+
const enterForLoop = () => (inForLoop = true)
158+
const exitForLoop = () => (inForLoop = false)
159+
160+
return {
161+
FunctionExpression: enterExpression,
162+
'FunctionExpression:exit': exitExpression,
163+
ArrowFunctionExpression: enterExpression,
164+
'ArrowFunctionExpression:exit': exitExpression,
165+
ForStatement: enterForLoop,
166+
'ForStatement:exit': exitForLoop,
167+
ForInStatement: enterForLoop,
168+
'ForInStatement:exit': exitForLoop,
169+
ForOfStatement: enterForLoop,
170+
'ForOfStatement:exit': exitForLoop,
171+
CallExpression(node: TSESTree.CallExpression) {
172+
const vitestFnCall = parseVitestFnCall(node, context)
173+
174+
if (vitestFnCall?.type === 'test') {
175+
inTestCaseCall = true
176+
return
177+
}
178+
179+
if (vitestFnCall?.type === 'expect' && inTestCaseCall) {
180+
if (expressionDepth === 1 && isFirstStatement(node) && vitestFnCall.head.node.parent?.type === AST_NODE_TYPES.MemberExpression && vitestFnCall.members.length === 1 &&
181+
['assertions', 'hasAssertions'].includes(getAccessorValue(vitestFnCall.members[0]))) {
182+
checkExpectHasAssertions(vitestFnCall, node)
183+
hasExpectAssertAsFirstStatement = true
184+
}
185+
186+
if (inForLoop)
187+
hasExpectInLoop = true
188+
189+
if (expressionDepth > 1)
190+
hasExpectInCallBack = true
191+
}
192+
},
193+
194+
'CallExpression:exit'(node: TSESTree.CallExpression) {
195+
if (!isTypeOfVitestFnCall(node, context, ['test']))
196+
return
197+
198+
inTestCaseCall = false
199+
200+
if (node.arguments.length < 2)
201+
return
202+
203+
const [, secondArg] = node.arguments
204+
205+
if (!isFunction(secondArg) || !shouldCheckFunction(secondArg))
206+
return
207+
208+
hasExpectInLoop = false
209+
hasExpectInCallBack = false
210+
211+
if (hasExpectAssertAsFirstStatement) {
212+
hasExpectAssertAsFirstStatement = false
213+
214+
return
215+
}
216+
217+
const suggestions: Array<[MessageIds, string]> = []
218+
219+
if (secondArg.body.type === AST_NODE_TYPES.BlockStatement) {
220+
suggestions.push(['suggestAddingHasAssertions', 'expect.hasAssertions();'],
221+
['suggestAddingAssertions', 'expect.assertions();'])
222+
}
223+
224+
context.report({
225+
messageId: 'haveExpectAssertions',
226+
node,
227+
suggest: suggestions.map(([messageId, text]) => ({
228+
messageId,
229+
fix: fixer =>
230+
fixer.insertTextBeforeRange(
231+
[secondArg.body.range[0] + 1, secondArg.body.range[1]],
232+
text
233+
)
234+
}))
235+
})
236+
}
237+
}
238+
}
239+
})
+298
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
import rule, { RULE_NAME } from '../src/rules/prefer-expect-assertions'
2+
import { ruleTester } from './ruleTester'
3+
4+
ruleTester.run(RULE_NAME, rule, {
5+
valid: [
6+
'test("it1", () => {expect.assertions(0);})',
7+
'test("it1", function() {expect.assertions(0);})',
8+
'test("it1", function() {expect.hasAssertions();})',
9+
'it("it1", function() {expect.assertions(0);})',
10+
'test("it1")',
11+
'itHappensToStartWithIt("foo", function() {})',
12+
'testSomething("bar", function() {})',
13+
'it(async () => {expect.assertions(0);})',
14+
{
15+
code: `
16+
const expectNumbersToBeGreaterThan = (numbers, value) => {
17+
for (let number of numbers) {
18+
expect(number).toBeGreaterThan(value);
19+
}
20+
};
21+
22+
it('returns numbers that are greater than two', function () {
23+
expectNumbersToBeGreaterThan(getNumbers(), 2);
24+
});
25+
`,
26+
options: [{ onlyFunctionsWithExpectInLoop: true }]
27+
},
28+
{
29+
code: `
30+
it("returns numbers that are greater than five", function () {
31+
expect.assertions(2);
32+
for (const number of getNumbers()) {
33+
expect(number).toBeGreaterThan(5);
34+
}
35+
});
36+
`,
37+
options: [{ onlyFunctionsWithExpectInLoop: true }]
38+
},
39+
{
40+
code: `it("returns things that are less than ten", function () {
41+
expect.hasAssertions();
42+
43+
for (const thing in things) {
44+
expect(thing).toBeLessThan(10);
45+
}
46+
});`,
47+
options: [{ onlyFunctionsWithExpectInLoop: true }]
48+
}
49+
],
50+
invalid: [
51+
{
52+
code: 'it("it1", () => foo())',
53+
errors: [
54+
{
55+
messageId: 'haveExpectAssertions',
56+
column: 1,
57+
line: 1,
58+
suggestions: null
59+
}
60+
]
61+
},
62+
{
63+
code: 'it(\'resolves\', () => expect(staged()).toBe(true));',
64+
errors: [
65+
{
66+
messageId: 'haveExpectAssertions',
67+
column: 1,
68+
line: 1,
69+
suggestions: null
70+
}
71+
]
72+
},
73+
{
74+
code: 'it(\'resolves\', async () => expect(await staged()).toBe(true));',
75+
errors: [
76+
{
77+
messageId: 'haveExpectAssertions',
78+
column: 1,
79+
line: 1,
80+
suggestions: null
81+
}
82+
]
83+
},
84+
{
85+
code: 'it("it1", () => {})',
86+
errors: [
87+
{
88+
messageId: 'haveExpectAssertions',
89+
column: 1,
90+
line: 1,
91+
suggestions: [
92+
{
93+
messageId: 'suggestAddingHasAssertions',
94+
output: 'it("it1", () => {expect.hasAssertions();})'
95+
},
96+
{
97+
messageId: 'suggestAddingAssertions',
98+
output: 'it("it1", () => {expect.assertions();})'
99+
}
100+
]
101+
}
102+
]
103+
},
104+
{
105+
code: 'it("it1", () => { foo()})',
106+
errors: [
107+
{
108+
messageId: 'haveExpectAssertions',
109+
column: 1,
110+
line: 1,
111+
suggestions: [
112+
{
113+
messageId: 'suggestAddingHasAssertions',
114+
output: 'it("it1", () => {expect.hasAssertions(); foo()})'
115+
},
116+
{
117+
messageId: 'suggestAddingAssertions',
118+
output: 'it("it1", () => {expect.assertions(); foo()})'
119+
}
120+
]
121+
}
122+
]
123+
},
124+
{
125+
code: 'it("it1", function() {var a = 2;})',
126+
errors: [
127+
{
128+
messageId: 'haveExpectAssertions',
129+
column: 1,
130+
line: 1,
131+
suggestions: [
132+
{
133+
messageId: 'suggestAddingHasAssertions',
134+
output:
135+
'it("it1", function() {expect.hasAssertions();var a = 2;})'
136+
},
137+
{
138+
messageId: 'suggestAddingAssertions',
139+
output: 'it("it1", function() {expect.assertions();var a = 2;})'
140+
}
141+
]
142+
}
143+
]
144+
},
145+
{
146+
code: 'it("it1", function() {expect.assertions();})',
147+
errors: [
148+
{
149+
messageId: 'assertionsRequiresOneArgument',
150+
column: 30,
151+
line: 1,
152+
suggestions: []
153+
}
154+
]
155+
},
156+
{
157+
code: 'it("it1", function() {expect.assertions(1,2);})',
158+
errors: [
159+
{
160+
messageId: 'assertionsRequiresOneArgument',
161+
column: 43,
162+
line: 1,
163+
suggestions: [
164+
{
165+
messageId: 'suggestRemovingExtraArguments',
166+
output: 'it("it1", function() {expect.assertions(1,);})'
167+
}
168+
]
169+
}
170+
]
171+
},
172+
{
173+
code: 'it("it1", function() {expect.assertions(1,2,);})',
174+
errors: [
175+
{
176+
messageId: 'assertionsRequiresOneArgument',
177+
column: 43,
178+
line: 1,
179+
suggestions: [
180+
{
181+
messageId: 'suggestRemovingExtraArguments',
182+
output: 'it("it1", function() {expect.assertions(1,);})'
183+
}
184+
]
185+
}
186+
]
187+
},
188+
{
189+
code: 'it("it1", function() {expect.assertions("1");})',
190+
errors: [
191+
{
192+
messageId: 'assertionsRequiresNumberArgument',
193+
column: 41,
194+
line: 1,
195+
suggestions: []
196+
}
197+
]
198+
},
199+
{
200+
code: 'it("it1", function() {expect.hasAssertions("1");})',
201+
errors: [
202+
{
203+
messageId: 'hasAssertionsTakesNoArguments',
204+
column: 30,
205+
line: 1,
206+
suggestions: [
207+
{
208+
messageId: 'suggestRemovingExtraArguments',
209+
output: 'it("it1", function() {expect.hasAssertions();})'
210+
}
211+
]
212+
}
213+
]
214+
},
215+
{
216+
code: 'it("it1", function() {expect.hasAssertions("1",);})',
217+
errors: [
218+
{
219+
messageId: 'hasAssertionsTakesNoArguments',
220+
column: 30,
221+
line: 1,
222+
suggestions: [
223+
{
224+
messageId: 'suggestRemovingExtraArguments',
225+
output: 'it("it1", function() {expect.hasAssertions();})'
226+
}
227+
]
228+
}
229+
]
230+
},
231+
{
232+
code: 'it("it1", function() {expect.hasAssertions("1", "2");})',
233+
errors: [
234+
{
235+
messageId: 'hasAssertionsTakesNoArguments',
236+
column: 30,
237+
line: 1,
238+
suggestions: [
239+
{
240+
messageId: 'suggestRemovingExtraArguments',
241+
output: 'it("it1", function() {expect.hasAssertions();})'
242+
}
243+
]
244+
}
245+
]
246+
},
247+
{
248+
code: `it("it1", () => {
249+
expect.hasAssertions();
250+
251+
for (const number of getNumbers()) {
252+
expect(number).toBeGreaterThan(0);
253+
}
254+
});
255+
256+
it("it1", () => {
257+
for (const number of getNumbers()) {
258+
expect(number).toBeGreaterThan(0);
259+
}
260+
});`,
261+
options: [{ onlyFunctionsWithExpectInLoop: true }],
262+
errors: [
263+
{
264+
messageId: 'haveExpectAssertions',
265+
column: 6,
266+
line: 9,
267+
}
268+
]
269+
},
270+
{
271+
code: `it("returns numbers that are greater than four", async () => {
272+
for (const number of await getNumbers()) {
273+
expect(number).toBeGreaterThan(4);
274+
}
275+
});
276+
277+
it("returns numbers that are greater than five", () => {
278+
for (const number of getNumbers()) {
279+
expect(number).toBeGreaterThan(5);
280+
}
281+
});
282+
`,
283+
options: [{ onlyFunctionsWithExpectInLoop: true }],
284+
errors: [
285+
{
286+
messageId: 'haveExpectAssertions',
287+
column: 1,
288+
line: 1,
289+
},
290+
{
291+
messageId: 'haveExpectAssertions',
292+
column: 4,
293+
line: 7,
294+
},
295+
],
296+
},
297+
]
298+
})

‎vitest.config.ts ‎vitest.config.mts

File renamed without changes.

0 commit comments

Comments
 (0)
Please sign in to comment.