UNPKG

jest-codemods

Version:

Codemods for migrating test files to Jest

194 lines (193 loc) 8.39 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); var finale_1 = __importDefault(require("../utils/finale")); // The ascending ordering for which setup function should be used. For example, // if there are only `beforeEach` blocks, then that should be used to setup // the test context. However, if there are any `beforeAll` blocks, the test // context must be initialized in a `beforeAll` block so that it runs before. var rankedSetupFunctionNames = ['beforeEach', 'beforeAll']; var testFunctionNames = [ 'after', 'afterEach', 'it', 'test', 'afterAll', 'before', ].concat(rankedSetupFunctionNames); var allFunctionNames = ['describe'].concat(testFunctionNames); var ignoredIdentifiers = ['retries', 'skip', 'slow', 'timeout']; var contextName = 'testContext'; var jasmineThis = function (fileInfo, api, options) { var j = api.jscodeshift; var root = j(fileInfo.source); // Track the index of the most general hook that references `this`. This is // necessary because the setup hook that initializes the testing context must // run before the testing context is referenced. var setupFunctionNameIndex = 0; function isFunctionExpressionWithinSpecificFunctions(path, acceptedFunctionNames) { if (!path || !path.parentPath || !Array.isArray(path.parentPath.value)) { return false; } var callExpressionPath = path.parentPath.parentPath; var isWithin = !!callExpressionPath && !!callExpressionPath.value && callExpressionPath.value.callee && callExpressionPath.value.callee.type === 'Identifier' && acceptedFunctionNames.indexOf(callExpressionPath.value.callee.name) > -1; // Keep track of the setup function with the highest precedence. When the // function that that we are in is one of the setup function names and it's // of higher precedence, then update to setup the test context with the // correct setup function. if (isWithin && rankedSetupFunctionNames.indexOf(callExpressionPath.value.callee.name) > setupFunctionNameIndex) { setupFunctionNameIndex = rankedSetupFunctionNames.indexOf(callExpressionPath.value.callee.name); } return isWithin; } function isWithinObjectOrClass(path) { var invalidParentTypes = ['Property', 'MethodDefinition']; var currentPath = path; while (currentPath && currentPath.value && invalidParentTypes.indexOf(currentPath.value.type) === -1) { currentPath = currentPath.parentPath; } return currentPath ? invalidParentTypes.indexOf(currentPath.value.type) > -1 : false; } function isWithinSpecificFunctions(path, acceptedFunctionNames, matchAll) { if (!matchAll) { // Do not replace within functions declared as object properties or class methods // See `transforms plain functions within lifecycle methods` test if (isWithinObjectOrClass(path)) { return false; } } var currentPath = path; while (currentPath && currentPath.value && currentPath.value.type === 'MemberExpression') { currentPath = currentPath.parentPath; } return (isFunctionExpressionWithinSpecificFunctions(currentPath, acceptedFunctionNames) || (currentPath ? isWithinSpecificFunctions(currentPath.parentPath, testFunctionNames, false) : false)); } var getValidThisExpressions = function (node) { return j(node) .find(j.MemberExpression, { object: { type: 'ThisExpression', }, property: { name: function (name) { return ignoredIdentifiers.indexOf(name) === -1; }, }, }) .filter(function (path) { return isWithinSpecificFunctions(path, allFunctionNames, true); }); }; var mutateScope = function (ast, body) { var replacedIdentifiers = []; var updateThisExpressions = function () { return ast .find(j.MemberExpression, { object: { type: 'ThisExpression', }, }) .filter(function (path) { return isWithinSpecificFunctions(path, allFunctionNames, true); }) .replaceWith(replaceThisExpression) .size(); }; var replaceThisExpression = function (path) { var name = path.value.property.name; replacedIdentifiers.push(name); return j.memberExpression(j.identifier(contextName), j.identifier(name), false); }; var addDeclarations = function () { if (!replacedIdentifiers.length) { return; } body.unshift(j.expressionStatement(j.callExpression(j.identifier(rankedSetupFunctionNames[setupFunctionNameIndex]), [ j.arrowFunctionExpression([], j.blockStatement([ j.expressionStatement(j.assignmentExpression('=', j.identifier(contextName), j.objectExpression([]))), ])), ]))); body.unshift(j.variableDeclaration('let', [ j.variableDeclarator(j.identifier.from({ name: contextName, typeAnnotation: ['ts', 'tsx'].includes(options.parser) && /\.tsx?$/.test(fileInfo.path) ? j.typeAnnotation(j.anyTypeAnnotation()) : null, }), null), ])); }; updateThisExpressions(); addDeclarations(); }; var mutateDescribe = function (path) { var functionExpression = path.value.arguments.find(function (node) { return node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression'; }); var functionBody = functionExpression.body; var ast = j(functionBody); mutateScope(ast, functionBody.body); }; var updateRoot = function () { var topLevelLifecycleMethods = root .find(j.CallExpression, { callee: { type: 'Identifier', name: function (name) { return testFunctionNames.indexOf(name) > -1; }, }, }) // Find only lifecyle methods which are in the root scope .filter(function (path) { return path.parentPath.value.type === 'ExpressionStatement' && Array.isArray(path.parentPath.parentPath.value) && path.parentPath.parentPath.parentPath.value.type === 'Program'; }) .filter(function (path) { return getValidThisExpressions(path.value).size() > 0; }) .size(); if (topLevelLifecycleMethods > 0) { var path = root.get(); mutateScope(root, path.value.program.body); return 1; } return 0; }; var updateDescribes = function () { return root .find(j.CallExpression, { callee: { type: 'Identifier', name: 'describe', }, }) .filter(function (path) { return getValidThisExpressions(path.value).size() > 0; }) .forEach(mutateDescribe) .size(); }; var updateFunctionExpressions = function () { return root .find(j.FunctionExpression) .filter(function (path) { return isFunctionExpressionWithinSpecificFunctions(path, allFunctionNames); }) .filter(function (path) { return !path.value.generator; }) .replaceWith(function (path) { var newFn = j.arrowFunctionExpression(path.value.params, path.value.body, path.value.expression); newFn.async = path.value.async; return newFn; }) .size(); }; var mutations = updateRoot() + updateDescribes() + updateFunctionExpressions(); if (!mutations) { return null; } return (0, finale_1.default)(fileInfo, j, root, options); }; exports.default = jasmineThis;