jest-codemods
Version:
Codemods for migrating test files to Jest
359 lines (358 loc) • 15.9 kB
JavaScript
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = expectTransformer;
var consts_1 = require("../utils/consts");
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 matcherRenaming = {
toExist: 'toBeTruthy',
toNotExist: 'toBeFalsy',
toNotBe: 'not.toBe',
toNotEqual: 'not.toEqual',
toNotThrow: 'not.toThrow',
toBeA: 'toBeInstanceOf',
toBeAn: 'toBeInstanceOf',
toNotBeA: 'not.toBeInstanceOf',
toNotBeAn: 'not.toBeInstanceOf',
toNotMatch: 'not.toMatch',
toBeFewerThan: 'toBeLessThan',
toBeLessThanOrEqualTo: 'toBeLessThanOrEqual',
toBeMoreThan: 'toBeGreaterThan',
toBeGreaterThanOrEqualTo: 'toBeGreaterThanOrEqual',
toInclude: 'toContain',
toExclude: 'not.toContain',
toNotContain: 'not.toContain',
toNotInclude: 'not.toContain',
toNotHaveBeenCalled: 'not.toHaveBeenCalled',
};
var expectSpyFunctions = new Set(['createSpy', 'spyOn', 'isSpy', 'restoreSpies']);
var unsupportedSpyFunctions = new Set(['isSpy', 'restoreSpies']);
var unsupportedExpectProperties = new Set(['extend']);
var EXPECT = 'expect';
function splitChainedMatcherPath(j, path) {
if (path.parentPath.parentPath.node.type !== 'MemberExpression') {
return;
}
var pStatement = (0, recast_helpers_1.findParentOfType)(path, 'ExpressionStatement');
var pStatementNode = pStatement.node.original;
var expectCallExpression = path.node.object;
function splitChain(callExpression) {
var next = callExpression.callee.object;
if (!next) {
return;
}
j(path)
.closest(j.ExpressionStatement)
.insertAfter(j.expressionStatement(j.callExpression(j.memberExpression(j.callExpression(expectCallExpression.callee, __spreadArray([], __read(expectCallExpression.arguments), false)), callExpression.callee.property), callExpression.arguments)));
splitChain(next);
}
splitChain(pStatementNode.expression);
pStatement.prune();
}
function expectTransformer(fileInfo, api, options) {
var j = api.jscodeshift;
var ast = j(fileInfo.source);
var standaloneMode = options.standaloneMode;
if (!(0, imports_1.hasRequireOrImport)(j, ast, EXPECT) && !options.skipImportDetection) {
// No expect require/import were found
return fileInfo.source;
}
var expectFunctionName = (0, imports_1.getRequireOrImportName)(j, ast, EXPECT) || EXPECT;
if (!standaloneMode) {
(0, imports_1.removeRequireAndImport)(j, ast, EXPECT);
}
var logWarning = function (msg, node) { return (0, logger_1.default)(fileInfo, msg, node); };
function balanceMatcherNodeArguments(matcherNode, matcher, path) {
var newJestMatcherName = matcher.name.replace('not.', '');
var maxArgs = consts_1.JEST_MATCHER_TO_MAX_ARGS[newJestMatcherName];
if (typeof maxArgs === 'undefined') {
logWarning("Unknown matcher \"".concat(newJestMatcherName, "\""), path);
return;
}
if (matcherNode.arguments.length > maxArgs) {
// Try to remove assertion message
var lastArg = matcherNode.arguments[matcherNode.arguments.length - 1];
if (lastArg.type === 'Literal') {
matcherNode.arguments.pop();
}
}
if (matcherNode.arguments.length <= maxArgs) {
return;
}
logWarning("Too many arguments given to \"".concat(newJestMatcherName, "\". Expected max ").concat(maxArgs, " but got ").concat(matcherNode.arguments.length), path);
}
var getMatchers = function () {
return ast.find(j.MemberExpression, {
object: {
type: 'CallExpression',
callee: { type: 'Identifier', name: expectFunctionName },
},
property: { type: 'Identifier' },
});
};
var splitChainedMatchers = function () {
return getMatchers().forEach(function (path) {
splitChainedMatcherPath(j, path);
});
};
var updateMatchers = function () {
return getMatchers().forEach(function (path) {
if (!standaloneMode) {
path.parentPath.node.callee.object.callee.name = EXPECT;
}
var matcherNode = path.parentPath.node;
var matcher = path.node.property;
var matcherName = matcher.name;
var matcherArgs = matcherNode.arguments;
var expectArgs = path.node.object.arguments;
var isNot = matcherName.indexOf('Not') !== -1 || matcherName.indexOf('Exclude') !== -1;
if (matcherRenaming[matcherName]) {
matcher.name = matcherRenaming[matcherName];
}
switch (matcherName) {
case 'toBeA':
case 'toBeAn':
case 'toNotBeA':
case 'toNotBeAn': {
if (matcherArgs[0].type === 'Literal') {
expectArgs[0] = j.unaryExpression('typeof', expectArgs[0]);
matcher.name = isNot ? 'not.toBe' : 'toBe';
}
break;
}
case 'toContainKey':
case 'toExcludeKey':
case 'toIncludeKey':
case 'toNotContainKey':
case 'toNotIncludeKey': {
expectArgs[0] = j.template.expression(templateObject_1 || (templateObject_1 = __makeTemplateObject(["Object.keys(", ")"], ["Object.keys(", ")"])), expectArgs[0]);
matcher.name = isNot ? 'not.toContain' : 'toContain';
break;
}
case 'toContainKeys':
case 'toExcludeKeys':
case 'toIncludeKeys':
case 'toNotContainKeys':
case 'toNotIncludeKeys': {
var keys = matcherArgs[0];
matcherArgs[0] = j.identifier('e');
expectArgs[0] = j.template.expression(templateObject_2 || (templateObject_2 = __makeTemplateObject(["Object.keys(", ")"], ["Object.keys(", ")"])), expectArgs[0]);
matcher.name = isNot ? 'not.toContain' : 'toContain';
j(path.parentPath).replaceWith(j.template.expression(templateObject_3 || (templateObject_3 = __makeTemplateObject(["", ".forEach(e => {\n ", "\n})"], ["\\\n", ".forEach(e => {\n ", "\n})"])), keys, matcherNode));
break;
}
case 'toMatch':
case 'toNotMatch': {
// expect toMatch handles string, reg exp and object.
var _a = matcherArgs[0], name = _a.name, type = _a.type;
if (type === 'ObjectExpression' || type === 'Identifier') {
matcher.name = isNot ? 'not.toMatchObject' : 'toMatchObject';
if (type === 'Identifier') {
logWarning("Use \"toMatch\" if \"".concat(name, "\" is not an object"), path);
}
}
break;
}
}
balanceMatcherNodeArguments(matcherNode, matcher, path);
});
};
var updateSpies = function () {
ast
.find(j.CallExpression, {
callee: {
type: 'Identifier',
name: function (name) { return expectSpyFunctions.has(name); },
},
})
.forEach(function (_a) {
var value = _a.value;
value.callee = j.memberExpression(j.identifier(expectFunctionName), j.identifier(value.callee.name));
});
// Update expect.createSpy calls and warn about restoreSpies
ast
.find(j.MemberExpression, {
object: {
type: 'Identifier',
name: expectFunctionName,
},
property: { type: 'Identifier' },
})
.forEach(function (path) {
var name = path.value.property.name;
if (name === 'createSpy') {
path.value.property.name = 'fn';
}
if (unsupportedSpyFunctions.has(name)) {
logWarning("\"".concat(path.value.property.name, "\" is currently not supported"), path);
}
});
// Warn about expect.spyOn calls with variable assignment
ast
.find(j.MemberExpression, {
object: {
type: 'Identifier',
name: expectFunctionName,
},
property: { type: 'Identifier', name: 'spyOn' },
})
.forEach(function (path) {
var parentAssignment = (0, recast_helpers_1.findParentOfType)(path, 'VariableDeclarator') ||
(0, recast_helpers_1.findParentOfType)(path, 'AssignmentExpression');
if (!parentAssignment) {
logWarning("\"".concat(path.value.property.name, "\" without variable assignment might not work as expected (see https://facebook.github.io/jest/docs/jest-object.html#jestspyonobject-methodname)"), path);
}
});
// Update mock chain calls
var updateSpyProperty = function (path, property) {
if (!property) {
return;
}
if (property.name === 'andReturn') {
var callExpression = (0, recast_helpers_1.findParentCallExpression)(path, property.name).value;
callExpression.arguments = [
j.arrowFunctionExpression([j.identifier('()')], callExpression.arguments[0]),
];
}
if (property.name === 'andThrow') {
var callExpression = (0, recast_helpers_1.findParentCallExpression)(path, property.name).value;
var throughExpression = callExpression.arguments[0];
callExpression.arguments = [
j.arrowFunctionExpression([j.identifier('()')], j.blockStatement([j.throwStatement(throughExpression)])),
];
}
if (property.name === 'andCallThrough') {
var callExpression = (0, recast_helpers_1.findParentCallExpression)(path, property.name);
var innerCallExpression = callExpression.value.callee.object;
j(callExpression).replaceWith(innerCallExpression);
}
var propertyNameMap = {
andCall: 'mockImplementation',
andReturn: 'mockImplementation',
andThrow: 'mockImplementation',
calls: 'mock.calls',
reset: 'mockClear',
restore: 'mockReset',
};
var newPropertyName = propertyNameMap[property.name];
if (newPropertyName) {
property.name = newPropertyName;
}
// Remap spy.calls[x].arguments
var potentialArgumentsPath = path.parentPath.parentPath;
var potentialArgumentsNode = potentialArgumentsPath.value;
if (property.name === 'mock.calls' &&
potentialArgumentsNode.property &&
potentialArgumentsNode.property.name === 'arguments') {
var variableName = path.value.object.name;
var callsProperty = path.parentPath.value.property;
if (potentialArgumentsPath.parentPath.value.type !== 'MemberExpression') {
// spy.calls[x].arguments => spy.mock.calls[x]
potentialArgumentsPath.replace(j.memberExpression(j.memberExpression(j.identifier(variableName), j.identifier('mock.calls')), callsProperty, true));
return;
}
// spy.calls[x].arguments[y] => spy.mock.calls[x][y]
var outherNode = path.parentPath.parentPath.parentPath;
var argumentsProperty = outherNode.value.property;
outherNode.replace(j.memberExpression(j.memberExpression(j.memberExpression(j.identifier(variableName), j.identifier('mock.calls')), callsProperty, true), argumentsProperty, true));
}
};
var spyVariables = [];
ast
.find(j.MemberExpression, {
object: {
type: 'Identifier',
name: expectFunctionName,
},
property: {
type: 'Identifier',
name: function (name) { return consts_1.JEST_MOCK_PROPERTIES.has(name); },
},
})
.forEach(function (path) {
var spyVariable = (0, recast_helpers_1.findParentVariableDeclaration)(path);
if (spyVariable) {
spyVariables.push(spyVariable.value.id.name);
}
var property = path.parentPath.parentPath.value.property;
updateSpyProperty(path, property);
});
// Update spy variable methods
ast
.find(j.MemberExpression, {
object: {
type: 'Identifier',
name: function (name) { return spyVariables.indexOf(name) >= 0; },
},
})
.forEach(function (path) {
var property = path.value.property;
var spyProperty = null;
if (property.type === 'Identifier') {
// spy.calls.length
spyProperty = property;
}
if (property.type === 'Literal') {
// spies[0].calls.length
spyProperty = path.parentPath.value.property;
}
if (spyProperty) {
updateSpyProperty(path, spyProperty);
}
});
};
var checkForUnsupportedFeatures = function () {
return ast
.find(j.MemberExpression, {
object: {
name: expectFunctionName,
},
property: {
name: function (name) { return unsupportedExpectProperties.has(name); },
},
})
.forEach(function (path) {
logWarning("\"".concat(path.value.property.name, "\" is currently not supported"), path);
});
};
splitChainedMatchers();
updateMatchers();
updateSpies();
checkForUnsupportedFeatures();
return (0, finale_1.default)(fileInfo, j, ast, options, expectFunctionName);
}
var templateObject_1, templateObject_2, templateObject_3;
;