UNPKG

jest-codemods

Version:

Codemods for migrating test files to Jest

873 lines (870 loc) 34.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = transformer; var chai_chain_utils_1 = require("../utils/chai-chain-utils"); var finale_1 = __importDefault(require("../utils/finale")); var imports_1 = require("../utils/imports"); var logger_1 = __importDefault(require("../utils/logger")); var recast_helpers_1 = require("../utils/recast-helpers"); var sinon_helpers_1 = require("../utils/sinon-helpers"); var SINON_CALL_COUNT_METHODS = [ 'called', 'calledOnce', 'calledTwice', 'calledThrice', 'callCount', 'notCalled', ]; var CHAI_CHAIN_MATCHERS = new Set([ 'be', 'eq', 'eql', 'equal', 'equals', 'toBe', 'toEqual', 'toBeTruthy', 'toBeFalsy', ].map(function (a) { return a.toLowerCase(); })); var SINON_CALLED_WITH_METHODS = [ 'calledWith', 'notCalledWith', 'neverCalledWith', ]; var SINON_SPY_METHODS = ['spy', 'stub']; var SINON_MOCK_RESETS = { reset: 'mockReset', resetBehavior: 'mockReset', resetHistory: 'mockReset', restore: 'mockRestore', }; var SINON_GLOBAL_MOCK_RESETS = { reset: 'resetAllMocks', resetBehavior: 'resetAllMocks', resetHistory: 'resetAllMocks', restore: 'restoreAllMocks', }; var _sinonMockImpls = [ 'returns', 'returnsArg', 'resolves', 'rejects', 'throws', 'callsFake', 'callsArg', 'callsArgOn', 'callsArgWith', 'callsArgOnWith', ]; var SINON_MOCK_IMPLEMENTERS = new Set(_sinonMockImpls); var SINON_MOCK_IMPLS_TO_JEST = { returns: 'mockReturnValue', resolves: 'mockResolvedValue', rejects: 'mockRejectedValue', callsFake: 'mockImplementation', }; var SINON_MATCHERS = { array: 'Array', func: 'Function', number: 'Number', object: 'Object', string: 'String', }; var SINON_MATCHERS_WITH_ARGS = { array: 'object', func: 'function', number: 'number', object: 'object', string: 'string', }; var SINON_NTH_CALLS = new Set(['firstCall', 'secondCall', 'thirdCall', 'lastCall']); var SINON_ON_NTH_CALLS = new Set(['onFirstCall', 'onSecondCall', 'onThirdCall']); var EXPECT_PREFIXES = new Set(['to']); var isPrefix = function (name) { return EXPECT_PREFIXES.has(name); }; var isTypescript = function (parser) { return parser === 'tsx' || parser === 'ts'; }; /* expect(spy.called).to.be(true) -> expect(spy).toHaveBeenCalled() expect(spy.callCount).to.equal(2) -> expect(spy).toHaveBeenCalledTimes(2) expect(stub).toHaveProperty('callCount', 1) -> expect(stub).toHaveBeenCalledTimes(1) */ function transformCallCountAssertions(j, ast) { var chainContains = (0, chai_chain_utils_1.chainContainsUtil)(j); var getAllBefore = (0, chai_chain_utils_1.getNodeBeforeMemberExpressionUtil)(j); var createCall = (0, chai_chain_utils_1.createCallUtil)(j); ast .find(j.CallExpression, { callee: { type: j.MemberExpression.name, property: { name: function (name) { var _a; return CHAI_CHAIN_MATCHERS.has((_a = name.toLowerCase) === null || _a === void 0 ? void 0 : _a.call(name)); }, }, object: function (node) { return (0, sinon_helpers_1.isExpectSinonObject)(node, SINON_CALL_COUNT_METHODS) && (0, chai_chain_utils_1.isExpectCallUtil)(j, node); }, }, }) .replaceWith(function (np) { var _a, _b, _c; var node = np.node; var expectArg = (0, sinon_helpers_1.getExpectArg)(node.callee); // remove .called/.callCount/etc prop from expect argument // eg: expect(Api.get.callCount) -> expect(Api.get) j(np) .find(j.CallExpression, { callee: { name: 'expect' }, }) .forEach(function (np) { np.node.arguments = [expectArg.object]; }); /* handle `expect(spy.withArgs('foo').called).to.be(true)` -> `expect(spy.calledWith(1,2,3)).to.be(true)` and let subsequent transform fn take care of converting to the final form (ie: see `transformCalledWithAssertions`) */ if (((_b = (_a = expectArg.object.callee) === null || _a === void 0 ? void 0 : _a.property) === null || _b === void 0 ? void 0 : _b.name) === 'withArgs') { // change .withArgs() -> .calledWith() expectArg.object.callee.property.name = 'calledWith'; return node; } var expectArgSinonMethod = expectArg.property.name; var negated = chainContains('not', node.callee, isPrefix) || ((_c = node.arguments) === null || _c === void 0 ? void 0 : _c[0].value) === false; // eg: .to.be(false) var rest = getAllBefore(isPrefix, node.callee, 'should'); switch (expectArgSinonMethod) { case 'notCalled': return createCall('toHaveBeenCalled', [], rest, !negated); case 'calledThrice': return createCall('toHaveBeenCalledTimes', [j.literal(3)], rest, negated); case 'calledTwice': return createCall('toHaveBeenCalledTimes', [j.literal(2)], rest, negated); case 'calledOnce': return createCall('toHaveBeenCalledTimes', [j.literal(1)], rest, negated); case 'called': return createCall('toHaveBeenCalled', [], rest, negated); default: // eg: .callCount return createCall('toHaveBeenCalledTimes', node.arguments.length ? [node.arguments[0]] : [], rest, negated); } }); // expect(stub).toHaveProperty('callCount', 1) -> expect(stub).toHaveBeenCalledTimes(1) ast .find(j.CallExpression, { callee: { type: j.MemberExpression.name, property: { name: 'toHaveProperty', }, object: function (node) { return (0, chai_chain_utils_1.isExpectCallUtil)(j, node); }, }, arguments: function (args) { var _a; return ((_a = args === null || args === void 0 ? void 0 : args[0]) === null || _a === void 0 ? void 0 : _a.value) === 'callCount'; }, }) .replaceWith(function (np) { var value = np.value; var newArgs = value.arguments.slice(1); value.callee.property.name = 'toHaveBeenCalledTimes'; value.arguments = newArgs; return value; }); } /* expect(spy.calledWith(1, 2, 3)).to.be(true) -> expect(spy).toHaveBeenCalledWith(1, 2, 3); https://github.com/jordalgo/jest-codemods/blob/7de97c1d0370c7915cf5e5cc2a860bc5dd96744b/src/transformers/sinon.js#L267 */ function transformCalledWithAssertions(j, ast) { var chainContains = (0, chai_chain_utils_1.chainContainsUtil)(j); var getAllBefore = (0, chai_chain_utils_1.getNodeBeforeMemberExpressionUtil)(j); var createCall = (0, chai_chain_utils_1.createCallUtil)(j); ast .find(j.CallExpression, { callee: { type: j.MemberExpression.name, property: { name: function (name) { var _a; return CHAI_CHAIN_MATCHERS.has((_a = name.toLowerCase) === null || _a === void 0 ? void 0 : _a.call(name)); }, }, object: function (node) { return (0, sinon_helpers_1.isExpectSinonCall)(node, SINON_CALLED_WITH_METHODS) && (0, chai_chain_utils_1.isExpectCallUtil)(j, node); }, }, }) .replaceWith(function (np) { var _a, _b, _c; var node = np.node; var expectArg = (0, sinon_helpers_1.getExpectArg)(node.callee); // remove .calledWith() call from expect argument j(np) .find(j.CallExpression, { callee: { name: 'expect' }, }) .forEach(function (np) { np.node.arguments = [expectArg.callee.object]; }); var expectArgSinonMethod = (_b = (_a = expectArg.callee) === null || _a === void 0 ? void 0 : _a.property) === null || _b === void 0 ? void 0 : _b.name; var negated = chainContains('not', node.callee, isPrefix) || ((_c = node.arguments) === null || _c === void 0 ? void 0 : _c[0].value) === false; // eg: .to.be(false) var rest = getAllBefore(isPrefix, node.callee, 'should'); switch (expectArgSinonMethod) { case 'calledWith': return createCall('toHaveBeenCalledWith', expectArg.arguments, rest, negated); case 'notCalledWith': return createCall('toHaveBeenCalledWith', expectArg.arguments, rest, !negated); default: return node; } }); } /* sinon.stub(Api, 'get') -> jest.spyOn(Api, 'get') */ function transformStub(j, ast, sinonExpression, logWarning) { ast .find(j.CallExpression, { callee: { type: 'MemberExpression', property: { type: 'Identifier', name: function (name) { return SINON_SPY_METHODS.includes(name); }, }, object: { type: 'Identifier', name: sinonExpression, }, }, }) .replaceWith(function (np) { // stubbing/spyOn module var args = np.value.arguments; var propertyName = np.node.callee.property.name; if (args.length === 1 && propertyName === 'stub') { logWarning('stubbing all methods in an object is not supported; stub each one you care about individually', np); return np.value; } if (args.length >= 2) { var spyOn_1 = j.callExpression(j.memberExpression(j.identifier('jest'), j.identifier('spyOn')), args.slice(0, 2)); // add mockClear since jest doesn't reset the stub on re-declaration like sinon does spyOn_1 = j.callExpression(j.memberExpression(spyOn_1, j.identifier('mockClear')), []); // add mockImplementation call if (args.length >= 3) { spyOn_1 = j.callExpression(j.memberExpression(spyOn_1, j.identifier('mockImplementation')), [args[2]]); if (args.length >= 4) { logWarning("4+ arguments found in sinon.".concat(propertyName, " call; did you mean to use this many?"), np); } } else if (propertyName === 'stub') { var parent = (0, recast_helpers_1.findParentOfType)(np, j.VariableDeclaration.name) || (0, recast_helpers_1.findParentOfType)(np, j.ExpressionStatement.name); var hasMockImpls = j(parent) .find(j.CallExpression, { callee: { type: 'MemberExpression', property: { type: 'Identifier', name: function (name) { return SINON_MOCK_IMPLEMENTERS.has(name); }, }, }, }) .size() > 0; if (!hasMockImpls) { spyOn_1 = j.callExpression(j.memberExpression(spyOn_1, j.identifier('mockImplementation')), []); } } return spyOn_1; } // jest mock function return j.callExpression(j.identifier('jest.fn'), args); }); } function getMockImplReturn(j, sinonImpl) { var _a; var sinonMethodName = sinonImpl.callee.property.name; if (sinonMethodName.startsWith('callsArg')) { /* stub.callsArg(0) -> stub.mockImplementation((...args: any[]) => args[0]()) stub.callsArgOn(1, thisArg) -> stub.mockImplementation((...args: any[]) => args[1].call(thisArg)) stub.callsArgWith(2, arg1, arg2) -> stub.mockImplementation((...args: any[]) => args[2](arg1, arg2)) stub.callsArgOnWith(3, thisArg, arg1, arg2) -> stub.mockImplementation((...args: any[]) => args[3].call(thisArg, arg1, arg2)) */ if (sinonImpl.arguments.length < 1) return undefined; var argName = j.memberExpression(j.identifier('args'), sinonImpl.arguments[0], true); switch (sinonMethodName) { case 'callsArg': return j.callExpression(argName, []); case 'callsArgOn': return j.callExpression(j.memberExpression(argName, j.identifier('call')), [ sinonImpl.arguments[1], ]); case 'callsArgWith': return j.callExpression(argName, sinonImpl.arguments.slice(1)); case 'callsArgOnWith': return j.callExpression(j.memberExpression(argName, j.identifier('call')), sinonImpl.arguments.slice(1)); } } var args = sinonImpl.arguments.slice(0, 1); if (args.length === 0 && (sinonMethodName === 'rejects' || sinonMethodName === 'throws')) { args = [j.newExpression(j.identifier('Error'), [])]; } switch (sinonMethodName) { case 'returns': return (_a = args[0]) !== null && _a !== void 0 ? _a : j.identifier('undefined'); case 'returnsArg': return j.memberExpression(j.identifier('args'), args[0], true); case 'resolves': return j.callExpression(j.memberExpression(j.identifier('Promise'), j.identifier('resolve')), args); case 'rejects': return j.callExpression(j.memberExpression(j.identifier('Promise'), j.identifier('reject')), args); case 'throws': return j.throwStatement(args[0]); case 'callsFake': return j.callExpression(args[0], [j.spreadElement(j.identifier('args'))]); } } /** gets one of sinon mock implementers (returns/returnsArg/resolves/...) and returns jest equivalent */ function getMockImplReplacement(j, sinonImpl, parser, conditionalExpr) { var sinonMethodName = sinonImpl.callee.property.name; var isTypescript = parser === 'ts' || parser === 'tsx'; if (conditionalExpr === undefined && SINON_MOCK_IMPLS_TO_JEST[sinonMethodName] !== undefined) { sinonImpl.callee.property.name = SINON_MOCK_IMPLS_TO_JEST[sinonMethodName]; if (sinonMethodName === 'rejects' && sinonImpl.arguments.length === 0) { // mockRejectedValue without argument does not throw Error like sinon.rejects does, fix args sinonImpl.arguments = [j.newExpression(j.identifier('Error'), [])]; } return sinonImpl; } var implReturn = getMockImplReturn(j, sinonImpl); if (implReturn === undefined) { return sinonImpl; } var returnIsExpr = j(implReturn).isOfType(j.Expression); var returnBody = returnIsExpr && conditionalExpr === undefined ? implReturn : j.blockStatement([returnIsExpr ? j.returnStatement(implReturn) : implReturn]); if (conditionalExpr !== undefined) { returnBody = j.blockStatement([j.ifStatement(conditionalExpr, returnBody)]); } var mockImplementationArgs = j(returnBody).find(j.Identifier, { name: 'args' }).size() === 0 ? [] : [ j.spreadPropertyPattern(j.identifier.from({ name: 'args', typeAnnotation: isTypescript ? j.typeAnnotation(j.arrayTypeAnnotation(j.anyTypeAnnotation())) : null, })), ]; var mockImplementationFn = j.arrowFunctionExpression(mockImplementationArgs, returnBody); if (conditionalExpr === undefined) { sinonImpl.callee.property.name = 'mockImplementation'; sinonImpl.arguments = [mockImplementationFn]; return sinonImpl; } return j.callExpression(j.memberExpression(sinonImpl.callee.object.callee.object, j.identifier('mockImplementation')), [mockImplementationFn]); } /* transform .onCall(0), .on{First,Second,Third}Call() stub.onCall(4).return(biscuits) -> stub.mockImplementation(() => { if (stub.mock.calls.length === 3) return biscuits; }) stub.onFirstCall().returnArg(2) -> stub.mockImplementation((...args: any[]) => { if (stub.mock.calls.length === 0) return args[2]; }) */ function transformStubOnCalls(j, ast, parser) { ast .find(j.CallExpression, { callee: { object: { callee: { property: { name: function (n) { return n === 'onCall' || SINON_ON_NTH_CALLS.has(n); }, }, }, }, property: { name: function (n) { return SINON_MOCK_IMPLEMENTERS.has(n); }, }, }, }) .replaceWith(function (_a) { var node = _a.node; var index; switch (node.callee.object.callee.property.name) { case 'onCall': index = node.callee.object.arguments[0]; break; case 'onFirstCall': index = j.numericLiteral(0); break; case 'onSecondCall': index = j.numericLiteral(1); break; case 'onThirdCall': index = j.numericLiteral(2); break; } if (!index) return node; // `jest.spyOn` or `jest.fn` var mockFn = node.callee.object.callee.object; var callLengthConditionalExpression = j.binaryExpression('===', j.memberExpression(mockFn, j.identifier('mock.calls.length')), index); return getMockImplReplacement(j, node, parser, callLengthConditionalExpression); }); } /* stub.getCall(0) -> stub.mock.calls[0] stub.getCall(0).args[1] -> stub.mock.calls[0][1] stub.firstCall|lastCall|thirdCall|secondCall -> stub.mock.calls[n] */ function transformStubGetCalls(j, ast) { // transform .getCall ast .find(j.CallExpression, { callee: { property: { name: function (n) { return ['getCall', 'getCalls'].includes(n); }, }, }, }) .replaceWith(function (np) { var _a, _b, _c; var node = np.node; var withMockCall = j.memberExpression(j.memberExpression(node.callee.object, j.identifier('mock')), j.identifier('calls')); if (node.callee.property.name === 'getCall') { return j.memberExpression(withMockCall, // ensure is a literal to prevent something like: `calls.0[0]` j.literal((_c = (_b = (_a = node.arguments) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : 0)); } return withMockCall; }); // transform .nthCall ast .find(j.MemberExpression, { property: { name: function (name) { return SINON_NTH_CALLS.has(name); }, }, }) .replaceWith(function (np) { var node = np.node; var name = node.property.name; var createMockCall = function (n) { var nth = j.literal(n); return j.memberExpression(j.memberExpression(node, j.identifier('calls')), nth); }; node.property.name = 'mock'; switch (name) { case 'firstCall': return createMockCall(0); case 'secondCall': return createMockCall(1); case 'thirdCall': return createMockCall(2); case 'lastCall': { return j.memberExpression(node, j.identifier('lastCall')); } } return node; }); // transform .args[n] expression ast // match on .args, not the more specific .args[n] .find(j.MemberExpression, { property: { name: 'args', }, }) .replaceWith(function (np) { var node = np.node; // if contains .mock.calls already, can safely remove .args if ((0, sinon_helpers_1.expressionContainsProperty)(node, 'mock') && ((0, sinon_helpers_1.expressionContainsProperty)(node, 'calls') || (0, sinon_helpers_1.expressionContainsProperty)(node, 'lastCall'))) { return np.node.object; } /* replace .args with mock.calls, handles: stub.args[0][0] -> stub.mock.calls[0][0] */ return j.memberExpression(np.node.object, j.identifier('mock.calls')); }); } /* handles: .withArgs .returns .returnsArg */ function transformMock(j, ast, parser) { // stub.withArgs(111).returns('foo') => stub.mockImplementation((...args) => { if (args[0] === '111') return 'foo' }) ast .find(j.CallExpression, { callee: { object: { callee: { property: { name: 'withArgs', }, }, }, property: { name: function (n) { return SINON_MOCK_IMPLEMENTERS.has(n); }, }, }, }) .replaceWith(function (np) { var node = np.node; // `jest.spyOn` or `jest.fn` var mockFn = node.callee.object.callee.object; var mockImplementationArgs = node.callee.object.arguments; // unsupported/untransformable .withArgs, just remove .withArgs from chain if (!(mockImplementationArgs === null || mockImplementationArgs === void 0 ? void 0 : mockImplementationArgs.length)) { node.callee = j.memberExpression(mockFn, node.callee.property); return node; } var isSinonMatcherArg = function (arg) { var _a, _b, _c, _d; return arg.type === 'MemberExpression' && ((_b = (_a = arg.object) === null || _a === void 0 ? void 0 : _a.object) === null || _b === void 0 ? void 0 : _b.name) === 'sinon' && ((_d = (_c = arg.object) === null || _c === void 0 ? void 0 : _c.property) === null || _d === void 0 ? void 0 : _d.name) === 'match'; }; // generate conditional expression to match args used in .mockImplementation var mockImplementationConditionalExpression = mockImplementationArgs .map(function (arg, i) { var argName = j.memberExpression(j.identifier('args'), j.literal(i), true); // handle sinon matchers if (isSinonMatcherArg(arg)) { var matcherType = SINON_MATCHERS_WITH_ARGS[arg.property.name]; // `sinon.match.object` -> `typeof args[0] === 'object'` if (matcherType) { return j.binaryExpression('===', j.unaryExpression('typeof', argName), j.stringLiteral(matcherType)); } // handle `sinon.match.any` - check for total number of args, eg: `args.length >= ${expectedArgs} return j.binaryExpression('>=', j.memberExpression(j.identifier('args'), j.identifier('length')), j.literal(mockImplementationArgs.length)); } return j.binaryExpression('===', argName, arg); }) .reduce(function (logicalExp, binExp, i) { if (i === 0) { return binExp; } return j.logicalExpression('&&', logicalExp, binExp); }); return getMockImplReplacement(j, node, parser, mockImplementationConditionalExpression); }); // any remaining sinon mock impl (returns, returnsArg, etc.) ast .find(j.CallExpression, { callee: { type: 'MemberExpression', property: { name: function (n) { return SINON_MOCK_IMPLEMENTERS.has(n); }, }, }, }) .replaceWith(function (_a) { var node = _a.node; return getMockImplReplacement(j, node, parser); }); } /* handles mock resets/clears/etc: sinon.restore() -> jest.restoreAllMocks() stub.restore() -> stub.mockRestore() stub.reset() -> stub.mockReset() */ function transformMockResets(j, ast) { ast .find(j.CallExpression, { callee: { type: 'MemberExpression', object: { type: 'Identifier', name: 'sinon', }, property: { type: 'Identifier', name: function (name) { return Object.hasOwn(SINON_GLOBAL_MOCK_RESETS, name); }, }, }, }) .forEach(function (_a) { var node = _a.node; node.callee.object.name = 'jest'; node.callee.property.name = SINON_GLOBAL_MOCK_RESETS[node.callee.property.name]; }); ast .find(j.CallExpression, { callee: { type: 'MemberExpression', property: { type: 'Identifier', name: function (name) { return Object.hasOwn(SINON_MOCK_RESETS, name); }, }, }, }) .forEach(function (np) { var name = SINON_MOCK_RESETS[np.node.callee.property.name]; np.node.callee.property.name = name; }); } /* sinon.assert.called(spy) -> expect(spy).toHaveBeenCalled() sinon.assert.calledOnce(spy) -> expect(spy).toHaveBeenCalledTimes(1) sinon.assert.calledWith(spy, arg1, arg2) -> expect(spy).toHaveBeenCalledWith(arg1, arg2) */ function transformAssert(j, ast) { ast .find(j.CallExpression, { type: 'CallExpression', callee: { type: 'MemberExpression', object: { type: 'MemberExpression', object: { type: 'Identifier', name: 'sinon', }, property: { type: 'Identifier', name: 'assert', }, }, property: { type: 'Identifier', name: function (n) { return SINON_CALL_COUNT_METHODS.includes(n) || SINON_CALLED_WITH_METHODS.includes(n); }, }, }, }) .replaceWith(function (np) { var args = np.node.arguments; var name = np.node.callee.property.name; var matcher; switch (name) { case 'called': matcher = j.callExpression(j.identifier('toHaveBeenCalled'), []); break; case 'notCalled': matcher = j.callExpression(j.memberExpression(j.identifier('not'), j.identifier('toHaveBeenCalled')), []); break; case 'calledOnce': matcher = j.callExpression(j.identifier('toHaveBeenCalledTimes'), [ j.numericLiteral(1), ]); break; case 'calledTwice': matcher = j.callExpression(j.identifier('toHaveBeenCalledTimes'), [ j.numericLiteral(2), ]); break; case 'calledThrice': matcher = j.callExpression(j.identifier('toHaveBeenCalledTimes'), [ j.numericLiteral(3), ]); break; case 'callCount': matcher = j.callExpression(j.identifier('toHaveBeenCalledTimes'), [args[1]]); break; case 'calledWith': matcher = j.callExpression(j.identifier('toHaveBeenCalledWith'), args.slice(1)); break; case 'neverCalledWith': matcher = j.callExpression(j.memberExpression(j.identifier('not'), j.identifier('toHaveBeenCalledWith')), args.slice(1)); break; } return j.memberExpression(j.callExpression(j.identifier('expect'), [args[0]]), matcher); }); } /* sinon.match({ ... }) -> expect.objectContaining({ ... }) // .any. matches: sinon.match.[any|number|string|object|func|array] -> expect.any(type) */ function transformMatch(j, ast) { ast .find(j.CallExpression, { callee: { type: 'MemberExpression', object: { type: 'Identifier', name: 'sinon', }, property: { type: 'Identifier', name: 'match', }, }, }) .replaceWith(function (np) { var args = np.node.arguments; return j.callExpression(j.identifier('expect.objectContaining'), args); }); ast .find(j.MemberExpression, { type: 'MemberExpression', object: { object: { name: 'sinon', }, property: { name: 'match', }, }, }) .replaceWith(function (np) { var name = np.node.property.name; var constructorType = SINON_MATCHERS[name]; if (constructorType) { return j.callExpression(j.identifier('expect.any'), [ j.identifier(constructorType), ]); } return j.callExpression(j.identifier('expect.anything'), []); }); } function transformMockTimers(j, ast) { // sinon.useFakeTimers() -> jest.useFakeTimers() // sinon.useFakeTimers(new Date(...)) -> jest.useFakeTimers().setSystemTime(new Date(...)) ast .find(j.CallExpression, { callee: { object: { name: 'sinon', }, property: { name: 'useFakeTimers', }, }, }) .forEach(function (np) { var _a, _b, _c, _d, _e, _f; var node = np.node; node.callee.object.name = 'jest'; // handle real system time if ((_a = node.arguments) === null || _a === void 0 ? void 0 : _a.length) { var args = node.arguments; node.arguments = []; node = j.callExpression(j.memberExpression(node, j.identifier('setSystemTime')), args); } // if `const clock = sinon.useFakeTimers()`, remove variable dec var parentAssignment = (0, recast_helpers_1.findParentOfType)(np, j.VariableDeclaration.name) || (0, recast_helpers_1.findParentOfType)(np, j.AssignmentExpression.name); if (parentAssignment) { // clock = sinon.useFakeTimers() if (((_b = parentAssignment.value) === null || _b === void 0 ? void 0 : _b.type) === j.AssignmentExpression.name) { var varName = (_c = parentAssignment.value.left) === null || _c === void 0 ? void 0 : _c.name; // clock = sinon.useFakeTimers() -> sinon.useFakeTimers() parentAssignment.parentPath.value.expression = node; // remove global variable declaration var varNp = (_f = (_e = (_d = np.scope.lookup(varName)) === null || _d === void 0 ? void 0 : _d.getBindings()) === null || _e === void 0 ? void 0 : _e[varName]) === null || _f === void 0 ? void 0 : _f[0]; if (varNp) { (0, sinon_helpers_1.modifyVariableDeclaration)(varNp, null); } // const clock = sinon.useFakeTimers() -> sinon.useFakeTimers() } else if (parentAssignment.parentPath.name === 'body') { (0, sinon_helpers_1.modifyVariableDeclaration)(np, j.expressionStatement(node)); } } }); // clock.tick(n) -> jest.advanceTimersByTime(n) ast .find(j.CallExpression, { callee: { object: { type: 'Identifier', }, property: { name: 'tick', }, }, }) .forEach(function (np) { var node = np.node; node.callee.object.name = 'jest'; node.callee.property.name = 'advanceTimersByTime'; }); /* `stub.restore` shares the same property name as `sinon.useFakeTimers().restore` so only transform those with `clock` object which seems to be the common name used for mock timers throughout our codebase */ // clock.restore() -> jest.useRealTimers() ast .find(j.CallExpression, { callee: { object: { name: 'clock', }, property: { name: 'restore', }, }, }) .forEach(function (np) { var node = np.node; node.callee.object.name = 'jest'; node.callee.property.name = 'useRealTimers'; }); } // let stub: sinon.SinonStub -> let stub: jest.Mock // let spy: sinon.SinonSpy -> let spy: jest.SpyInstance function transformTypes(j, ast, parser) { if (!isTypescript(parser)) return; ast .find(j.TSTypeReference, { typeName: { left: { name: 'sinon', }, right: { name: 'SinonStub', }, }, }) .forEach(function (np) { np.node.typeName.left.name = 'jest'; np.node.typeName.right.name = 'Mock'; }); ast .find(j.TSTypeReference, { typeName: { left: { name: 'sinon', }, right: { name: 'SinonSpy', }, }, }) .forEach(function (np) { np.node.typeName.left.name = 'jest'; np.node.typeName.right.name = 'SpyInstance'; }); } function transformer(fileInfo, api, options) { var j = api.jscodeshift; var ast = j(fileInfo.source); var sinonExpression = (0, imports_1.removeRequireAndImport)(j, ast, 'sinon-sandbox') || (0, imports_1.removeRequireAndImport)(j, ast, 'sinon'); if (!sinonExpression) { if (!options.skipImportDetection) { return fileInfo.source; } return null; } var logWarning = function (msg, node) { return (0, logger_1.default)(fileInfo, msg, node); }; transformStub(j, ast, sinonExpression, logWarning); transformStubOnCalls(j, ast, options.parser); transformMockTimers(j, ast); transformMock(j, ast, options.parser); transformMockResets(j, ast); transformCallCountAssertions(j, ast); transformCalledWithAssertions(j, ast); transformAssert(j, ast); transformMatch(j, ast); transformStubGetCalls(j, ast); transformTypes(j, ast, options.parser); return (0, finale_1.default)(fileInfo, j, ast, options); }