Skip to content

Commit

Permalink
fix: expect.hasAssertions() / expect.assertions() - Does not work whe…
Browse files Browse the repository at this point in the history
…n 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
  • Loading branch information
y-hsgw authored Dec 17, 2024
1 parent 27f859f commit efe79c9
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 32 deletions.
6 changes: 0 additions & 6 deletions src/rules/prefer-expect-assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,6 @@ export default createEslintRule<Options[], MessageIds>({

const [, secondArg] = node.arguments

if (secondArg?.type === AST_NODE_TYPES.ArrowFunctionExpression && secondArg.params.length) {
if (secondArg?.params[0].type === AST_NODE_TYPES.ObjectPattern) {
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
}
}

if (!isFunction(secondArg) || !shouldCheckFunction(secondArg))
return

Expand Down
11 changes: 8 additions & 3 deletions src/rules/require-local-test-context-for-concurrent-snapshots.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
import { createEslintRule, isSupportedAccessor } from '../utils'
import { isTypeOfVitestFnCall } from '../utils/parse-vitest-fn-call'
import { isTypeOfVitestFnCall, parseVitestFnCall } from '../utils/parse-vitest-fn-call'

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

Expand All @@ -21,8 +21,13 @@ export default createEslintRule({
create(context) {
return {
CallExpression(node) {
const isNotAnAssertion = !isTypeOfVitestFnCall(node, context, ['expect'])
if (isNotAnAssertion) return
const vitestFnCall = parseVitestFnCall(node, context)
if (vitestFnCall === null)
return
if (vitestFnCall.type !== "expect")
return
if (vitestFnCall.type === "expect" && vitestFnCall.head.type === "testContext")
return

const isNotASnapshotAssertion = ![
'toMatchSnapshot',
Expand Down
31 changes: 20 additions & 11 deletions src/utils/parse-vitest-fn-call.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AST_NODE_TYPES, TSESLint, TSESTree } from '@typescript-eslint/utils'
import { DescribeAlias, HookName, ModifierName, TestCaseName } from './types'
import { ValidVitestFnCallChains } from './valid-vitest-fn-call-chains'
import { AccessorNode, getAccessorValue, getStringValue, isIdentifier, isStringNode, isSupportedAccessor } from '.'
import { AccessorNode, getAccessorValue, getStringValue, isFunction, isIdentifier, isStringNode, isSupportedAccessor } from '.'

export type VitestFnType =
| 'test'
Expand All @@ -17,7 +17,7 @@ export type VitestFnType =
interface ResolvedVitestFn {
original: string | null
local: string
type: 'import' | 'global'
type: 'import' | 'global' | 'testContext'
}

interface ImportDetails {
Expand Down Expand Up @@ -309,17 +309,11 @@ export function getNodeChain(node: TSESTree.Node): AccessorNode[] | null {
return null
}

interface ResolvedVitestFnType {
original: string | null
local: string
type: 'import' | 'global'
}

const resolveVitestFn = (
context: TSESLint.RuleContext<string, unknown[]>,
node: TSESTree.Node,
node: TSESTree.CallExpression,
identifier: string
): ResolvedVitestFnType | null => {
): ResolvedVitestFn | null => {
const scope = context.sourceCode.getScope
? context.sourceCode.getScope(node)
: context.getScope()
Expand All @@ -328,6 +322,13 @@ const resolveVitestFn = (
if (maybeImport === 'local')
return null

if (maybeImport === "testContext")
return {
local: identifier,
original: null,
type: "testContext"
}

if (maybeImport) {
if (maybeImport.source === 'vitest') {
return {
Expand Down Expand Up @@ -364,7 +365,7 @@ const resolvePossibleAliasedGlobal = (
export const resolveScope = (
scope: TSESLint.Scope.Scope,
identifier: string
): ImportDetails | 'local' | null => {
): ImportDetails | 'local' | 'testContext' | null => {
let currentScope: TSESLint.Scope.Scope | null = scope

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

const objectParam = isFunction(def.node) ? def.node.params.find(params => params.type === AST_NODE_TYPES.ObjectPattern ) : undefined
if (objectParam) {
const property = objectParam.properties.find(property => property.type === AST_NODE_TYPES.Property)
const key = property?.key.type === AST_NODE_TYPES.Identifier ? property.key : undefined
if (key?.name === identifier)
return "testContext"
}

const importDetails = describePossibleImportDef(def)

if (importDetails?.local === identifier)
Expand Down
65 changes: 53 additions & 12 deletions tests/prefer-expect-assertions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ ruleTester.run(RULE_NAME, rule, {
expect(number).toBeGreaterThan(value);
}
};
it('returns numbers that are greater than two', function () {
expectNumbersToBeGreaterThan(getNumbers(), 2);
});
Expand Down Expand Up @@ -63,6 +63,47 @@ ruleTester.run(RULE_NAME, rule, {
}
]
},
{
code: `
it('my test description', ({ expect }) => {
const a = 1;
const b = 2;
expect(sum(a, b)).toBe(a + b);
})
`,
errors: [
{
messageId: 'haveExpectAssertions',
column: 1,
line: 2,
suggestions: [
{
messageId: 'suggestAddingHasAssertions',
output: `
it('my test description', ({ expect }) => {expect.hasAssertions();
const a = 1;
const b = 2;
expect(sum(a, b)).toBe(a + b);
})
`
},
{
messageId: 'suggestAddingAssertions',
output: `
it('my test description', ({ expect }) => {expect.assertions();
const a = 1;
const b = 2;
expect(sum(a, b)).toBe(a + b);
})
`
}
]
}
],
},
{
code: 'it(\'resolves\', () => expect(staged()).toBe(true));',
errors: [
Expand Down Expand Up @@ -251,12 +292,12 @@ ruleTester.run(RULE_NAME, rule, {
{
code: `it("it1", () => {
expect.hasAssertions();
for (const number of getNumbers()) {
expect(number).toBeGreaterThan(0);
}
});
it("it1", () => {
for (const number of getNumbers()) {
expect(number).toBeGreaterThan(0);
Expand All @@ -273,12 +314,12 @@ ruleTester.run(RULE_NAME, rule, {
messageId: 'suggestAddingHasAssertions',
output: `it("it1", () => {
expect.hasAssertions();
for (const number of getNumbers()) {
expect(number).toBeGreaterThan(0);
}
});
it("it1", () => {expect.hasAssertions();
for (const number of getNumbers()) {
expect(number).toBeGreaterThan(0);
Expand All @@ -289,12 +330,12 @@ ruleTester.run(RULE_NAME, rule, {
messageId: 'suggestAddingAssertions',
output: `it("it1", () => {
expect.hasAssertions();
for (const number of getNumbers()) {
expect(number).toBeGreaterThan(0);
}
});
it("it1", () => {expect.assertions();
for (const number of getNumbers()) {
expect(number).toBeGreaterThan(0);
Expand All @@ -311,7 +352,7 @@ ruleTester.run(RULE_NAME, rule, {
expect(number).toBeGreaterThan(4);
}
});
it("returns numbers that are greater than five", () => {
for (const number of getNumbers()) {
expect(number).toBeGreaterThan(5);
Expand All @@ -332,7 +373,7 @@ ruleTester.run(RULE_NAME, rule, {
expect(number).toBeGreaterThan(4);
}
});
it("returns numbers that are greater than five", () => {
for (const number of getNumbers()) {
expect(number).toBeGreaterThan(5);
Expand All @@ -347,7 +388,7 @@ ruleTester.run(RULE_NAME, rule, {
expect(number).toBeGreaterThan(4);
}
});
it("returns numbers that are greater than five", () => {
for (const number of getNumbers()) {
expect(number).toBeGreaterThan(5);
Expand All @@ -370,7 +411,7 @@ ruleTester.run(RULE_NAME, rule, {
expect(number).toBeGreaterThan(4);
}
});
it("returns numbers that are greater than five", () => {expect.hasAssertions();
for (const number of getNumbers()) {
expect(number).toBeGreaterThan(5);
Expand All @@ -385,7 +426,7 @@ ruleTester.run(RULE_NAME, rule, {
expect(number).toBeGreaterThan(4);
}
});
it("returns numbers that are greater than five", () => {expect.assertions();
for (const number of getNumbers()) {
expect(number).toBeGreaterThan(5);
Expand Down

0 comments on commit efe79c9

Please sign in to comment.