jest-codemods
Version:
Codemods for migrating test files to Jest
873 lines (870 loc) • 34.3 kB
JavaScript
"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);
}