diff --git a/src/rules/__tests__/prefer-spy-on.test.ts b/src/rules/__tests__/prefer-spy-on.test.ts index d418485c2..fd847b100 100644 --- a/src/rules/__tests__/prefer-spy-on.test.ts +++ b/src/rules/__tests__/prefer-spy-on.test.ts @@ -106,5 +106,38 @@ ruleTester.run('prefer-spy-on', rule, { }, ], }, + { + // https://github.com/jest-community/eslint-plugin-jest/issues/1304 + code: 'foo[bar] = jest.fn().mockReturnValue(undefined)', + output: + 'jest.spyOn(foo, bar).mockImplementation().mockReturnValue(undefined)', + errors: [ + { + messageId: 'useJestSpyOn', + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + // https://github.com/jest-community/eslint-plugin-jest/issues/1307 + code: ` + foo.bar = jest.fn().mockImplementation(baz => baz) + foo.bar = jest.fn(a => b).mockImplementation(baz => baz) + `, + output: ` + jest.spyOn(foo, 'bar').mockImplementation(baz => baz) + jest.spyOn(foo, 'bar').mockImplementation(baz => baz) + `, + errors: [ + { + messageId: 'useJestSpyOn', + type: AST_NODE_TYPES.AssignmentExpression, + }, + { + messageId: 'useJestSpyOn', + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, ], }); diff --git a/src/rules/prefer-spy-on.ts b/src/rules/prefer-spy-on.ts index 9b5786321..c400664cc 100644 --- a/src/rules/prefer-spy-on.ts +++ b/src/rules/prefer-spy-on.ts @@ -1,4 +1,4 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES, TSESLint, TSESTree } from '@typescript-eslint/utils'; import { createRule, getNodeName } from './utils'; const findNodeObject = ( @@ -39,6 +39,27 @@ const getJestFnCall = (node: TSESTree.Node): TSESTree.CallExpression | null => { return getJestFnCall(obj); }; +const getAutoFixMockImplementation = ( + jestFnCall: TSESTree.CallExpression, + context: TSESLint.RuleContext<'useJestSpyOn', unknown[]>, +): string => { + const hasMockImplementationAlready = + jestFnCall.parent?.type === AST_NODE_TYPES.MemberExpression && + jestFnCall.parent.property.type === AST_NODE_TYPES.Identifier && + jestFnCall.parent.property.name === 'mockImplementation'; + + if (hasMockImplementationAlready) { + return ''; + } + + const [arg] = jestFnCall.arguments; + const argSource = arg && context.getSourceCode().getText(arg); + + return argSource + ? `.mockImplementation(${argSource})` + : '.mockImplementation()'; +}; + export default createRule({ name: __filename, meta: { @@ -71,12 +92,13 @@ export default createRule({ messageId: 'useJestSpyOn', fix(fixer) { const leftPropQuote = - left.property.type === AST_NODE_TYPES.Identifier ? "'" : ''; - const [arg] = jestFnCall.arguments; - const argSource = arg && context.getSourceCode().getText(arg); - const mockImplementation = argSource - ? `.mockImplementation(${argSource})` - : '.mockImplementation()'; + left.property.type === AST_NODE_TYPES.Identifier && !left.computed + ? "'" + : ''; + const mockImplementation = getAutoFixMockImplementation( + jestFnCall, + context, + ); return [ fixer.insertTextBefore(left, `jest.spyOn(`),