jest-codemods
Version:
Codemods for migrating test files to Jest
194 lines (193 loc) • 8.39 kB
JavaScript
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;
;