Skip to content

Commit efe79c9

Browse files
authored
fix: expect.hasAssertions() / expect.assertions() - Does not work when using expect() bound to current test (#595)
* fix: ensure prefer-expect-assertions rule works correctly with test context * test: add test * fix: require-local-test-context-for-concurrent-snapshots rule
1 parent 27f859f commit efe79c9

4 files changed

+81
-32
lines changed

src/rules/prefer-expect-assertions.ts

-6
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,6 @@ export default createEslintRule<Options[], MessageIds>({
202202

203203
const [, secondArg] = node.arguments
204204

205-
if (secondArg?.type === AST_NODE_TYPES.ArrowFunctionExpression && secondArg.params.length) {
206-
if (secondArg?.params[0].type === AST_NODE_TYPES.ObjectPattern) {
207-
if (secondArg.params[0].properties[0].type === AST_NODE_TYPES.Property && secondArg.params[0].properties[0].key.type === AST_NODE_TYPES.Identifier && secondArg.params[0].properties[0].key.name === "expect") return
208-
}
209-
}
210-
211205
if (!isFunction(secondArg) || !shouldCheckFunction(secondArg))
212206
return
213207

src/rules/require-local-test-context-for-concurrent-snapshots.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
22
import { createEslintRule, isSupportedAccessor } from '../utils'
3-
import { isTypeOfVitestFnCall } from '../utils/parse-vitest-fn-call'
3+
import { isTypeOfVitestFnCall, parseVitestFnCall } from '../utils/parse-vitest-fn-call'
44

55
export const RULE_NAME = 'require-local-test-context-for-concurrent-snapshots'
66

@@ -21,8 +21,13 @@ export default createEslintRule({
2121
create(context) {
2222
return {
2323
CallExpression(node) {
24-
const isNotAnAssertion = !isTypeOfVitestFnCall(node, context, ['expect'])
25-
if (isNotAnAssertion) return
24+
const vitestFnCall = parseVitestFnCall(node, context)
25+
if (vitestFnCall === null)
26+
return
27+
if (vitestFnCall.type !== "expect")
28+
return
29+
if (vitestFnCall.type === "expect" && vitestFnCall.head.type === "testContext")
30+
return
2631

2732
const isNotASnapshotAssertion = ![
2833
'toMatchSnapshot',

src/utils/parse-vitest-fn-call.ts

+20-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { AST_NODE_TYPES, TSESLint, TSESTree } from '@typescript-eslint/utils'
22
import { DescribeAlias, HookName, ModifierName, TestCaseName } from './types'
33
import { ValidVitestFnCallChains } from './valid-vitest-fn-call-chains'
4-
import { AccessorNode, getAccessorValue, getStringValue, isIdentifier, isStringNode, isSupportedAccessor } from '.'
4+
import { AccessorNode, getAccessorValue, getStringValue, isFunction, isIdentifier, isStringNode, isSupportedAccessor } from '.'
55

66
export type VitestFnType =
77
| 'test'
@@ -17,7 +17,7 @@ export type VitestFnType =
1717
interface ResolvedVitestFn {
1818
original: string | null
1919
local: string
20-
type: 'import' | 'global'
20+
type: 'import' | 'global' | 'testContext'
2121
}
2222

2323
interface ImportDetails {
@@ -309,17 +309,11 @@ export function getNodeChain(node: TSESTree.Node): AccessorNode[] | null {
309309
return null
310310
}
311311

312-
interface ResolvedVitestFnType {
313-
original: string | null
314-
local: string
315-
type: 'import' | 'global'
316-
}
317-
318312
const resolveVitestFn = (
319313
context: TSESLint.RuleContext<string, unknown[]>,
320-
node: TSESTree.Node,
314+
node: TSESTree.CallExpression,
321315
identifier: string
322-
): ResolvedVitestFnType | null => {
316+
): ResolvedVitestFn | null => {
323317
const scope = context.sourceCode.getScope
324318
? context.sourceCode.getScope(node)
325319
: context.getScope()
@@ -328,6 +322,13 @@ const resolveVitestFn = (
328322
if (maybeImport === 'local')
329323
return null
330324

325+
if (maybeImport === "testContext")
326+
return {
327+
local: identifier,
328+
original: null,
329+
type: "testContext"
330+
}
331+
331332
if (maybeImport) {
332333
if (maybeImport.source === 'vitest') {
333334
return {
@@ -364,7 +365,7 @@ const resolvePossibleAliasedGlobal = (
364365
export const resolveScope = (
365366
scope: TSESLint.Scope.Scope,
366367
identifier: string
367-
): ImportDetails | 'local' | null => {
368+
): ImportDetails | 'local' | 'testContext' | null => {
368369
let currentScope: TSESLint.Scope.Scope | null = scope
369370

370371
while (currentScope !== null) {
@@ -373,6 +374,14 @@ export const resolveScope = (
373374
if (ref && ref.defs.length > 0) {
374375
const def = ref.defs[ref.defs.length - 1]
375376

377+
const objectParam = isFunction(def.node) ? def.node.params.find(params => params.type === AST_NODE_TYPES.ObjectPattern ) : undefined
378+
if (objectParam) {
379+
const property = objectParam.properties.find(property => property.type === AST_NODE_TYPES.Property)
380+
const key = property?.key.type === AST_NODE_TYPES.Identifier ? property.key : undefined
381+
if (key?.name === identifier)
382+
return "testContext"
383+
}
384+
376385
const importDetails = describePossibleImportDef(def)
377386

378387
if (importDetails?.local === identifier)

tests/prefer-expect-assertions.test.ts

+53-12
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ ruleTester.run(RULE_NAME, rule, {
2323
expect(number).toBeGreaterThan(value);
2424
}
2525
};
26-
26+
2727
it('returns numbers that are greater than two', function () {
2828
expectNumbersToBeGreaterThan(getNumbers(), 2);
2929
});
@@ -63,6 +63,47 @@ ruleTester.run(RULE_NAME, rule, {
6363
}
6464
]
6565
},
66+
{
67+
code: `
68+
it('my test description', ({ expect }) => {
69+
const a = 1;
70+
const b = 2;
71+
72+
expect(sum(a, b)).toBe(a + b);
73+
})
74+
`,
75+
errors: [
76+
{
77+
messageId: 'haveExpectAssertions',
78+
column: 1,
79+
line: 2,
80+
suggestions: [
81+
{
82+
messageId: 'suggestAddingHasAssertions',
83+
output: `
84+
it('my test description', ({ expect }) => {expect.hasAssertions();
85+
const a = 1;
86+
const b = 2;
87+
88+
expect(sum(a, b)).toBe(a + b);
89+
})
90+
`
91+
},
92+
{
93+
messageId: 'suggestAddingAssertions',
94+
output: `
95+
it('my test description', ({ expect }) => {expect.assertions();
96+
const a = 1;
97+
const b = 2;
98+
99+
expect(sum(a, b)).toBe(a + b);
100+
})
101+
`
102+
}
103+
]
104+
}
105+
],
106+
},
66107
{
67108
code: 'it(\'resolves\', () => expect(staged()).toBe(true));',
68109
errors: [
@@ -251,12 +292,12 @@ ruleTester.run(RULE_NAME, rule, {
251292
{
252293
code: `it("it1", () => {
253294
expect.hasAssertions();
254-
295+
255296
for (const number of getNumbers()) {
256297
expect(number).toBeGreaterThan(0);
257298
}
258299
});
259-
300+
260301
it("it1", () => {
261302
for (const number of getNumbers()) {
262303
expect(number).toBeGreaterThan(0);
@@ -273,12 +314,12 @@ ruleTester.run(RULE_NAME, rule, {
273314
messageId: 'suggestAddingHasAssertions',
274315
output: `it("it1", () => {
275316
expect.hasAssertions();
276-
317+
277318
for (const number of getNumbers()) {
278319
expect(number).toBeGreaterThan(0);
279320
}
280321
});
281-
322+
282323
it("it1", () => {expect.hasAssertions();
283324
for (const number of getNumbers()) {
284325
expect(number).toBeGreaterThan(0);
@@ -289,12 +330,12 @@ ruleTester.run(RULE_NAME, rule, {
289330
messageId: 'suggestAddingAssertions',
290331
output: `it("it1", () => {
291332
expect.hasAssertions();
292-
333+
293334
for (const number of getNumbers()) {
294335
expect(number).toBeGreaterThan(0);
295336
}
296337
});
297-
338+
298339
it("it1", () => {expect.assertions();
299340
for (const number of getNumbers()) {
300341
expect(number).toBeGreaterThan(0);
@@ -311,7 +352,7 @@ ruleTester.run(RULE_NAME, rule, {
311352
expect(number).toBeGreaterThan(4);
312353
}
313354
});
314-
355+
315356
it("returns numbers that are greater than five", () => {
316357
for (const number of getNumbers()) {
317358
expect(number).toBeGreaterThan(5);
@@ -332,7 +373,7 @@ ruleTester.run(RULE_NAME, rule, {
332373
expect(number).toBeGreaterThan(4);
333374
}
334375
});
335-
376+
336377
it("returns numbers that are greater than five", () => {
337378
for (const number of getNumbers()) {
338379
expect(number).toBeGreaterThan(5);
@@ -347,7 +388,7 @@ ruleTester.run(RULE_NAME, rule, {
347388
expect(number).toBeGreaterThan(4);
348389
}
349390
});
350-
391+
351392
it("returns numbers that are greater than five", () => {
352393
for (const number of getNumbers()) {
353394
expect(number).toBeGreaterThan(5);
@@ -370,7 +411,7 @@ ruleTester.run(RULE_NAME, rule, {
370411
expect(number).toBeGreaterThan(4);
371412
}
372413
});
373-
414+
374415
it("returns numbers that are greater than five", () => {expect.hasAssertions();
375416
for (const number of getNumbers()) {
376417
expect(number).toBeGreaterThan(5);
@@ -385,7 +426,7 @@ ruleTester.run(RULE_NAME, rule, {
385426
expect(number).toBeGreaterThan(4);
386427
}
387428
});
388-
429+
389430
it("returns numbers that are greater than five", () => {expect.assertions();
390431
for (const number of getNumbers()) {
391432
expect(number).toBeGreaterThan(5);

0 commit comments

Comments
 (0)