Skip to content

Commit 5e1e140

Browse files
ssssotaveritem
andauthored
feat(prefer-expect-assertions): add disallowHasAssertions option (#929)
* feat(prefer-expect-assertions): add disableHasAssertions option `expect.hasAssertions()` provides loose assertion checking, whereas `expect.assertions(N)` is strict. * fix formatting issues --------- Co-authored-by: Verite Mugabo <mugaboverite@gmail.com>
1 parent 792320a commit 5e1e140

3 files changed

Lines changed: 123 additions & 9 deletions

File tree

‎docs/rules/prefer-expect-assertions.md‎

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,12 @@ test('assertions first', () => {
4646

4747
<!-- begin auto-generated rule options list -->
4848

49-
| Name | Description | Type |
50-
| :---------------------------------- | :------------------------------------------------------------ | :------ |
51-
| `onlyFunctionsWithAsyncKeyword` | Only check test functions declared with the async keyword. | Boolean |
52-
| `onlyFunctionsWithExpectInCallback` | Only check test functions that contain `expect` in callbacks. | Boolean |
53-
| `onlyFunctionsWithExpectInLoop` | Only check test functions that contain `expect` inside loops. | Boolean |
49+
| Name | Description | Type |
50+
| :---------------------------------- | :--------------------------------------------------------------------------- | :------ |
51+
| `disallowHasAssertions` | Warn when `expect.hasAssertions()` is used instead of `expect.assertions()`. | Boolean |
52+
| `onlyFunctionsWithAsyncKeyword` | Only check test functions declared with the async keyword. | Boolean |
53+
| `onlyFunctionsWithExpectInCallback` | Only check test functions that contain `expect` in callbacks. | Boolean |
54+
| `onlyFunctionsWithExpectInLoop` | Only check test functions that contain `expect` inside loops. | Boolean |
5455

5556
<!-- end auto-generated rule options list -->
5657

@@ -126,3 +127,26 @@ test('assertions first', () => {
126127
})
127128
})
128129
```
130+
131+
`disallowHasAssertions` (default: `false`)
132+
133+
When `true`, `expect.hasAssertions()` will be reported in favor of `expect.assertions()`.
134+
Suggestions for missing assertions will only include `expect.assertions()`.
135+
136+
when this option is enabled the following code will be considered incorrect:
137+
138+
```js
139+
test('has assertions', () => {
140+
expect.hasAssertions()
141+
expect(value).toBe(1)
142+
})
143+
```
144+
145+
To fix this, use `expect.assertions(<number of assertions>)` instead:
146+
147+
```js
148+
test('has assertions', () => {
149+
expect.assertions(1)
150+
expect(value).toBe(1)
151+
})
152+
```

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

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type Options = {
1919
onlyFunctionsWithAsyncKeyword?: boolean
2020
onlyFunctionsWithExpectInLoop?: boolean
2121
onlyFunctionsWithExpectInCallback?: boolean
22+
disallowHasAssertions?: boolean
2223
}
2324

2425
const RULE_NAME = 'prefer-expect-assertions'
@@ -28,8 +29,10 @@ type MessageIds =
2829
| 'assertionsRequiresOneArgument'
2930
| 'assertionsRequiresNumberArgument'
3031
| 'haveExpectAssertions'
32+
| 'preferAssertionsOverHasAssertions'
3133
| 'suggestAddingHasAssertions'
3234
| 'suggestAddingAssertions'
35+
| 'suggestReplacingWithAssertions'
3336
| 'suggestRemovingExtraArguments'
3437

3538
const isFirstStatement = (node: TSESTree.CallExpression): boolean => {
@@ -72,9 +75,13 @@ export default createEslintRule<Options[], MessageIds>({
7275
assertionsRequiresNumberArgument: 'This argument should be a number',
7376
haveExpectAssertions:
7477
'Every test should have either `expect.assertions(<number of assertions>)` or `expect.hasAssertions()` as its first expression',
78+
preferAssertionsOverHasAssertions:
79+
'Prefer `expect.assertions(<number of assertions>)` over `expect.hasAssertions()`',
7580
suggestAddingHasAssertions: 'Add `expect.hasAssertions()`',
7681
suggestAddingAssertions:
7782
'Add `expect.assertions(<number of assertions>)`',
83+
suggestReplacingWithAssertions:
84+
'Replace with `expect.assertions(<number of assertions>)`',
7885
suggestRemovingExtraArguments: 'Remove extra arguments',
7986
},
8087
type: 'suggestion',
@@ -98,6 +105,11 @@ export default createEslintRule<Options[], MessageIds>({
98105
'Only check test functions that contain `expect` in callbacks.',
99106
type: 'boolean',
100107
},
108+
disallowHasAssertions: {
109+
description:
110+
'Warn when `expect.hasAssertions()` is used instead of `expect.assertions()`.',
111+
type: 'boolean',
112+
},
101113
},
102114
additionalProperties: false,
103115
},
@@ -107,6 +119,7 @@ export default createEslintRule<Options[], MessageIds>({
107119
onlyFunctionsWithAsyncKeyword: false,
108120
onlyFunctionsWithExpectInCallback: false,
109121
onlyFunctionsWithExpectInLoop: false,
122+
disallowHasAssertions: false,
110123
},
111124
],
112125
},
@@ -153,6 +166,18 @@ export default createEslintRule<Options[], MessageIds>({
153166
node: expectFnCall.matcher,
154167
suggest: [suggestRemovingExtraArguments(context, func, 0)],
155168
})
169+
} else if (options.disallowHasAssertions) {
170+
context.report({
171+
messageId: 'preferAssertionsOverHasAssertions',
172+
node: expectFnCall.matcher,
173+
suggest: [
174+
{
175+
messageId: 'suggestReplacingWithAssertions',
176+
fix: (fixer) =>
177+
fixer.replaceText(expectFnCall.members[0], 'assertions'),
178+
},
179+
],
180+
})
156181
}
157182
return
158183
}
@@ -266,10 +291,16 @@ export default createEslintRule<Options[], MessageIds>({
266291

267292
if (secondArg.body.type === AST_NODE_TYPES.BlockStatement) {
268293
const prefix = testContextName ? `${testContextName}.` : ''
269-
suggestions.push(
270-
['suggestAddingHasAssertions', `${prefix}expect.hasAssertions();`],
271-
['suggestAddingAssertions', `${prefix}expect.assertions();`],
272-
)
294+
if (!options.disallowHasAssertions) {
295+
suggestions.push([
296+
'suggestAddingHasAssertions',
297+
`${prefix}expect.hasAssertions();`,
298+
])
299+
}
300+
suggestions.push([
301+
'suggestAddingAssertions',
302+
`${prefix}expect.assertions();`,
303+
])
273304
}
274305

275306
context.report({

‎tests/prefer-expect-assertions.test.ts‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,5 +536,64 @@ it('my test description', (context) => {context.expect.assertions();
536536
},
537537
],
538538
},
539+
{
540+
code: 'it("it1", function() {expect.hasAssertions();})',
541+
options: [{ disallowHasAssertions: true }],
542+
errors: [
543+
{
544+
messageId: 'preferAssertionsOverHasAssertions',
545+
column: 30,
546+
line: 1,
547+
suggestions: [
548+
{
549+
messageId: 'suggestReplacingWithAssertions',
550+
output: 'it("it1", function() {expect.assertions();})',
551+
},
552+
],
553+
},
554+
],
555+
},
556+
{
557+
code: `it('my test description', ({ expect }) => {
558+
expect.hasAssertions();
559+
const a = 1;
560+
expect(a).toBe(1);
561+
})`,
562+
options: [{ disallowHasAssertions: true }],
563+
errors: [
564+
{
565+
messageId: 'preferAssertionsOverHasAssertions',
566+
column: 10,
567+
line: 2,
568+
suggestions: [
569+
{
570+
messageId: 'suggestReplacingWithAssertions',
571+
output: `it('my test description', ({ expect }) => {
572+
expect.assertions();
573+
const a = 1;
574+
expect(a).toBe(1);
575+
})`,
576+
},
577+
],
578+
},
579+
],
580+
},
581+
{
582+
code: 'it("it1", () => {})',
583+
options: [{ disallowHasAssertions: true }],
584+
errors: [
585+
{
586+
messageId: 'haveExpectAssertions',
587+
column: 1,
588+
line: 1,
589+
suggestions: [
590+
{
591+
messageId: 'suggestAddingAssertions',
592+
output: 'it("it1", () => {expect.assertions();})',
593+
},
594+
],
595+
},
596+
],
597+
},
539598
],
540599
})

0 commit comments

Comments
 (0)