UNPKG

jest-codemods

Version:

Codemods for migrating test files to Jest

359 lines (358 loc) 15.9 kB
"use strict"; 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;