UNPKG

jest-codemods

Version:

Codemods for migrating test files to Jest

810 lines (809 loc) 32.3 kB
"use strict"; 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 __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = jasmineGlobals; var finale_1 = __importDefault(require("../utils/finale")); var logger_1 = __importDefault(require("../utils/logger")); function jasmineGlobals(fileInfo, api, options) { var j = api.jscodeshift; var root = j(fileInfo.source); var emptyArrowFn = j('() => {}').__paths[0].value.program.body[0].expression; var logWarning = function (msg, path) { return (0, logger_1.default)(fileInfo, msg, path); }; root .find(j.MemberExpression, { object: { type: 'CallExpression', callee: { type: 'Identifier', name: 'expect' }, }, property: { type: 'Identifier' }, }) .forEach(function (path) { var _a; var isNegatedMatcher = path.node.property.name === 'not'; var matcher = isNegatedMatcher ? path.parent.node.property : path.node.property; var expectArgs = isNegatedMatcher ? path.parent.node.object.object.arguments : path.node.object.arguments; var matcherNode = isNegatedMatcher ? path.parent.parentPath.node : path.parentPath.node; var matcherArgs = (_a = matcherNode.arguments) !== null && _a !== void 0 ? _a : []; switch (matcher.name) { case 'toBeTrue': { matcherArgs[0] = j.literal(true); matcher.name = 'toBe'; break; } case 'toBeFalse': { matcherArgs[0] = j.literal(false); matcher.name = 'toBe'; break; } case 'toBePositiveInfinity': { matcherArgs[0] = j.literal(Infinity); matcher.name = 'toBe'; break; } case 'toBeNegativeInfinity': { matcherArgs[0] = j.literal(-Infinity); matcher.name = 'toBe'; break; } case 'toHaveSize': { matcher.name = 'toHaveLength'; break; } case 'toHaveClass': { expectArgs[0] = j.callExpression(j.memberExpression(j.memberExpression(expectArgs[0], j.identifier('classList')), j.identifier('contains')), __spreadArray([], __read(matcherArgs), false)); matcherArgs[0] = j.literal(true); matcher.name = 'toBe'; break; } case 'toHaveBeenCalledOnceWith': { expectArgs[0] = j.memberExpression(j.memberExpression(expectArgs[0], j.identifier('mock')), j.identifier('calls')); matcher.name = 'toEqual'; matcherNode.arguments = [j.arrayExpression([j.arrayExpression(matcherArgs)])]; break; } case 'toHaveBeenCalledBefore': { var getMinInvocationOrder = function (spy) { return j.callExpression(j.memberExpression(j.identifier('Math'), j.identifier('min')), [ j.spreadElement(j.memberExpression(j.memberExpression(spy, j.identifier('mock')), j.identifier('invocationOrder'))), ]); }; expectArgs[0] = getMinInvocationOrder(expectArgs[0]); matcherArgs[0] = getMinInvocationOrder(matcherArgs[0]); matcher.name = 'toBeLessThan'; break; } case 'toHaveSpyInteractions': { expectArgs[0] = j.callExpression(j.memberExpression(j.callExpression(j.memberExpression(j.identifier('Object'), j.identifier('values')), [expectArgs[0]]), j.identifier('some')), [ j.arrowFunctionExpression([j.identifier('spy')], j.optionalMemberExpression(j.optionalMemberExpression(j.memberExpression(j.identifier('spy'), j.identifier('mock')), j.identifier('calls'), false, true), j.identifier('length'))), ]); matcherArgs[0] = j.literal(true); matcher.name = 'toBe'; break; } } }); root .find(j.MemberExpression, { object: { type: 'CallExpression', callee: { type: 'Identifier', name: 'expectAsync' }, }, property: { type: 'Identifier' }, }) .forEach(function (path) { var parentNode = path.parent.node; var matcher = path.node.property; parentNode.callee.object.callee.name = 'expect'; switch (matcher.name) { case 'toBeResolvedTo': { var argument = parentNode.arguments[0]; parentNode.callee.property.name = argument ? 'resolves.toBe' : 'resolves.toBeUndefined'; break; } case 'toBeResolved': { parentNode.callee.property.name = 'resolves.toBeUndefined'; break; } case 'toBeRejected': { parentNode.callee.property.name = 'rejects.toBeDefined'; break; } case 'toBeRejectedWith': { parentNode.callee.property.name = 'rejects.toEqual'; break; } case 'toBeRejectedWithError': { parentNode.callee.property.name = 'rejects.toThrow'; break; } } }); root .find(j.CallExpression, { // find `jasmine.createSpy(*).and.*()` expressions callee: { type: 'MemberExpression', object: { type: 'MemberExpression', property: { name: 'and' }, object: { type: 'CallExpression', callee: { type: 'MemberExpression', property: { type: 'Identifier', name: 'createSpy', }, object: { type: 'Identifier', name: 'jasmine', }, }, }, }, }, }) .forEach(function (path) { var spyType = path.node.callee.property.name; switch (spyType) { // `jasmine.createSpy().and.callFake(*)` is equivalent of // `jest.fn(*)` case 'callFake': { path.node.callee = j.memberExpression(j.identifier('jest'), j.identifier('fn')); break; } // `jasmine.createSpy().and.returnValue(*)` is equivalent of // `jest.fn(() => *)` case 'returnValue': { path.node.callee = j.memberExpression(j.identifier('jest'), j.identifier('fn')); path.node.arguments = [j.arrowFunctionExpression([], path.node.arguments[0])]; break; } // This is transformed by the *.and.*() expression handling below case 'rejectWith': case 'resolveTo': { break; } default: { logWarning("Unsupported Jasmine functionality \"jasmine.createSpy().and.".concat(spyType, "\"."), path); break; } } }); root .find(j.CallExpression, { // find all `*.and.returnValue()` callee: { type: 'MemberExpression', property: { type: 'Identifier', name: 'returnValue' }, object: { type: 'MemberExpression', property: { type: 'Identifier', name: 'and' }, }, }, }) .forEach(function (path) { // Rename mock function path.node.callee.property.name = 'mockReturnValue'; // Remove '.and.' in between path.node.callee.object = path.node.callee.object.object; }); root .find(j.CallExpression, { // find all other `jasmine.createSpy` calls callee: { type: 'MemberExpression', property: { type: 'Identifier', name: 'createSpy', }, object: { type: 'Identifier', name: 'jasmine', }, }, }) .forEach(function (path) { // make it `jest.fn()` path.node.callee = j.memberExpression(j.identifier('jest'), j.identifier('fn')); path.node.arguments = []; }); root // find all global `spyOn` calls that are standalone expressions. // e.g. // spyOn(stuff) // but not // spyOn(stuff).and.callThrough(); .find(j.ExpressionStatement, { expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'spyOn', }, }, }) .forEach(function (path) { path.node.expression = j.callExpression(j.memberExpression(path.node.expression, // add .mockImplementation(() => {}); call // because jasmine spy's default is to mock the return value, // whereas jest calls through by default. j.identifier('mockImplementation')), [emptyArrowFn]); }); root .find(j.CallExpression, { // find *.and.*() expressions, e.g. // - `spyOn().and.callThrough()`, // - `spyOn().and.callFake(..)` // - `existingSpy.and.callFake(..)` // - `spyOn().and.returnValue(..)` // - `existingSpy.and.returnValue(..)` // - `spyOn().and.resolveTo(..)` // - `existingSpy.and.rejectWith(..)` callee: { type: 'MemberExpression', object: { type: 'MemberExpression', property: { name: 'and' }, }, }, }) .forEach(function (path) { var e_1, _a; var spyType = path.node.callee.property.name; switch (spyType) { // if it's `*.and.callThrough()` we should remove // `and.callThrough()` because jest calls through by default case 'callThrough': { // if this comes from an `Identifier` (e.g. `existingSpy.and.callThrough()`), // we assume the intent is to restore an existing spy // to its original implementation using `*.mockRestore()` if (path.node.callee.object.object.type === 'Identifier') { path.node.callee.object = path.node.callee.object.object; path.node.callee.property.name = 'mockRestore'; } else { // otherwise, we just remove the `.and.callThrough()` // since this is the default behavior in jest j(path).replaceWith(path.node.callee.object.object); } break; } // if it's `*.and.callFake()`, replace with jest's // equivalent `*.mockImplementation(); case 'callFake': { path.node.callee.object = path.node.callee.object.object; path.node.callee.property.name = 'mockImplementation'; break; } // `*.and.returnValue()` is equivalent of jest // `*.mockReturnValue()` case 'returnValue': { path.node.callee.object = path.node.callee.object.object; path.node.callee.property.name = 'mockReturnValue'; break; } case 'returnValues': { var mockReturnValuesCall = path.node.callee.object.object; try { for (var _b = __values(path.node.arguments), _c = _b.next(); !_c.done; _c = _b.next()) { var argument = _c.value; mockReturnValuesCall = j.callExpression(j.memberExpression(mockReturnValuesCall, j.identifier('mockReturnValueOnce')), [argument]); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_1) throw e_1.error; } } j(path).replaceWith(mockReturnValuesCall); break; } // `*.and.resolveTo()` is equivalent of jest // `*.mockResolvedValue()` case 'resolveTo': { path.node.callee.object = path.node.callee.object.object; path.node.callee.property.name = 'mockResolvedValue'; break; } // `*.and.rejectWith()` is equivalent of jest // `*.mockRejectedValue()` case 'rejectWith': { path.node.callee.object = path.node.callee.object.object; path.node.callee.property.name = 'mockRejectedValue'; break; } case 'throwError': { var throwArg = path.node.arguments[0]; var isStringLiteral = throwArg.type === 'Literal' && typeof throwArg.value === 'string'; path.node.callee.object = path.node.callee.object.object; path.node.callee.property.name = 'mockImplementation'; path.node.arguments = [ j.arrowFunctionExpression([], isStringLiteral ? j.blockStatement([ j.throwStatement(j.newExpression(j.identifier('Error'), [throwArg])), ]) : j.blockStatement([j.throwStatement(throwArg)])), ]; break; } case 'stub': { if (path.parentPath.node.type === 'MemberExpression') { j(path).replaceWith(path.node.callee.object.object); } else { path.node.callee.object = path.node.callee.object.object; path.node.callee.property.name = 'mockImplementation'; path.node.arguments = [emptyArrowFn]; } break; } } }); root .find(j.CallExpression, { // find all `spyOn` calls callee: { type: 'Identifier', name: 'spyOn' }, }) .forEach(function (path) { // and make them `jest.spyOn()` path.node.callee = j.memberExpression(j.identifier('jest'), j.identifier('spyOn')); }); root .find(j.CallExpression, { callee: { type: 'Identifier', name: 'spyOnProperty' }, }) .forEach(function (path) { path.node.callee = j.memberExpression(j.identifier('jest'), j.identifier('spyOn')); // explicitly add third parameter, which is defaulted as 'get' in jasmine if (path.node.arguments.length === 2) { path.node.arguments.push(j.literal('get')); } }); root .find(j.CallExpression, { // find all `*.calls.count()` callee: { type: 'MemberExpression', property: { type: 'Identifier', name: 'count' }, object: { type: 'MemberExpression', property: { type: 'Identifier', name: 'calls' }, }, }, }) .forEach(function (path) { // replace `.count()` with `.length` path.node.callee.property.name = 'length'; // add extra `.mock` property that jest uses: // stuff.calls.count() -> stuff.mock.calls.length path.node.callee.object.object = j.memberExpression(path.node.callee.object.object, j.identifier('mock')); j(path).replaceWith(path.node.callee); }); root .find(j.CallExpression, { // find all `*.calls.reset()` callee: { type: 'MemberExpression', property: { type: 'Identifier', name: 'reset' }, object: { type: 'MemberExpression', property: { type: 'Identifier', name: 'calls' }, }, }, }) .forEach(function (path) { j(path).replaceWith(j.callExpression(j.memberExpression(path.node.callee.object.object, j.identifier('mockReset')), [])); }); root .find(j.MemberExpression, { // find all `stuff.callCount` property: { type: 'Identifier', name: 'callCount', }, }) .forEach(function (path) { // and make them `stuff.mock.calls.length` path.node.property.name = 'length'; path.node.object = j.memberExpression(path.node.object, j.identifier('mock')); path.node.object = j.memberExpression(path.node.object, j.identifier('calls')); }); root .find(j.MemberExpression, { // find `stuff.mostRecentCall` property: { type: 'Identifier', name: 'mostRecentCall', }, }) .forEach(function (path) { // turn it into `stuff.mock.calls[stuff.mock.calls.length - 1]` path.node.object = j.memberExpression(path.node.object, j.identifier('mock')); path.node.object = j.memberExpression(path.node.object, j.identifier('calls')); path.node.property = j.binaryExpression('-', j.memberExpression(path.node.object, j.identifier('length')), j.literal(1)); path.node.computed = true; }); root .find(j.CallExpression, { // find `*.calls.mostRecent()` callee: { type: 'MemberExpression', object: { type: 'MemberExpression', property: { type: 'Identifier', name: 'calls', }, }, property: { type: 'Identifier', name: 'mostRecent', }, }, }) .forEach(function (path) { var expressionMockCalls = j.memberExpression(j.memberExpression(path.node.callee.object.object, j.identifier('mock')), j.identifier('calls')); // turn it into `*.mock.calls[*.mock.calls.length - 1]` j(path).replaceWith(j.memberExpression(expressionMockCalls, j.binaryExpression('-', j.memberExpression(expressionMockCalls, j.identifier('length')), j.literal(1)), true)); }); root .find(j.CallExpression, { // find `*.calls.allArgs()` callee: { type: 'MemberExpression', object: { type: 'MemberExpression', property: { type: 'Identifier', name: 'calls', }, }, property: { type: 'Identifier', name: 'allArgs', }, }, }) .forEach(function (path) { j(path).replaceWith(j.memberExpression(j.memberExpression(path.node.callee.object.object, j.identifier('mock')), j.identifier('calls'))); }); root // find anything that accesses property on `args` // like `stuff.mostRecentCall.args[0]` .find(j.MemberExpression, { object: { type: 'MemberExpression', property: { type: 'Identifier', name: 'args', }, }, }) .forEach(function (path) { // remove args, since jest calls are array of arrays // `stuff.mostRecentCall[0]` path.node.object.object && (path.node.object = path.node.object.object); }); root .find(j.MemberExpression, { // find `stuff.argsForCall[*]` object: { type: 'MemberExpression', property: { type: 'Identifier', name: 'argsForCall', }, }, }) .forEach(function (path) { // make them `stuff.mock.calls[*] path.node.object.object && (path.node.object = path.node.object.object); path.node.object = j.memberExpression(path.node.object, j.identifier('mock')); path.node.object = j.memberExpression(path.node.object, j.identifier('calls')); }); root .find(j.CallExpression, { // find `*.calls.argsFor(index)` callee: { type: 'MemberExpression', object: { type: 'MemberExpression', property: { type: 'Identifier', name: 'calls', }, }, property: { type: 'Identifier', name: 'argsFor', }, }, }) .forEach(function (path) { var expressionMockCalls = j.memberExpression(j.memberExpression(path.node.callee.object.object, j.identifier('mock')), j.identifier('calls')); var expressionIndex = path.node.arguments[0].type === 'Identifier' ? j.memberExpression(expressionMockCalls, path.node.arguments[0], true) : j.memberExpression(expressionMockCalls, j.literal(path.node.arguments[0].value)); // make it `*.mock.calls[index]` j(path).replaceWith(expressionIndex); }); root .find(j.MemberExpression, { // replace `andCallFake` with `mockImplementation` property: { type: 'Identifier', name: 'andCallFake', }, }) .forEach(function (path) { path.node.property.name = 'mockImplementation'; }); root .find(j.MemberExpression, { // replace `andReturn` with `mockReturnValue` property: { type: 'Identifier', name: 'andReturn', }, }) .forEach(function (path) { path.node.property.name = 'mockReturnValue'; }); root // fin all `andCallThrough` and delete them since // jest mocks call through by default .find(j.CallExpression, { callee: { type: 'MemberExpression', property: { type: 'Identifier', name: 'andCallThrough', }, }, }) .forEach(function (path) { j(path).replaceWith(path.node.callee.object); }); root .find(j.CallExpression, { // find all `jasmine.clock()` callee: { type: 'MemberExpression', object: { type: 'CallExpression', callee: { object: { type: 'Identifier', name: 'jasmine', }, property: { type: 'Identifier', name: 'clock', }, }, }, }, }) .forEach(function (path) { var usageType = path.node.callee.property.name; switch (usageType) { case 'install': { // make it `jest.useFakeTimers()` path.node.callee = j.memberExpression(j.identifier('jest'), j.identifier('useFakeTimers')); break; } case 'uninstall': { // make it `jest.useRealTimers()` path.node.callee = j.memberExpression(j.identifier('jest'), j.identifier('useRealTimers')); break; } case 'tick': { // make it `jest.advanceTimersByTime(ms)` path.node.callee = j.memberExpression(j.identifier('jest'), j.identifier('advanceTimersByTime')); break; } case 'mockDate': { // make it `jest.setSystemTime(date)` path.node.callee = j.memberExpression(j.identifier('jest'), j.identifier('setSystemTime')); break; } default: { logWarning("Unsupported Jasmine functionality \"jasmine.clock().".concat(usageType, "\"."), path); break; } } }); var jasmineToExpectFunctionNames = [ 'any', 'anything', 'arrayContaining', 'objectContaining', 'stringMatching', ]; jasmineToExpectFunctionNames.forEach(function (functionName) { // jasmine.<jasmineToExpectFunctionName>(*) root .find(j.CallExpression, { callee: { type: 'MemberExpression', object: { type: 'Identifier', name: 'jasmine', }, property: { type: 'Identifier', name: functionName, }, }, }) .forEach(function (path) { // `jasmine.<jasmineToExpectFunctionName>(*)` is equivalent of // `expect.<jasmineToExpectFunctionName>(*)` path.node.callee.object.name = 'expect'; }); }); root .find(j.CallExpression, { callee: { type: 'MemberExpression', object: { type: 'Identifier', name: 'jasmine', }, property: { type: 'Identifier', name: 'createSpyObj', }, }, }) .filter(function (path) { var args = path.node.arguments; var isArrayOrObjectExpression = function (arg) { return arg.type === 'ArrayExpression' || arg.type === 'ObjectExpression'; }; var firstArgumentIsMethodNames = isArrayOrObjectExpression(args[0]); if (firstArgumentIsMethodNames) { // ensure that optional baseName is always filled, to simplify arguments handling args.unshift(j.literal('')); } var _a = __read(args, 3), spyObjMethods = _a[1], spyObjProperties = _a[2]; return ((args.length === 2 || args.length === 3) && isArrayOrObjectExpression(spyObjMethods) && (spyObjProperties === undefined || isArrayOrObjectExpression(spyObjProperties))); }) .forEach(function (path) { var typeParameters = path.node.typeParameters; var _a = __read(path.node.arguments, 3), spyObjMethods = _a[1], spyObjProperties = _a[2]; var properties = spyObjMethods.type === 'ArrayExpression' ? spyObjMethods.elements.map(function (arg) { return j.objectProperty(j.literal(arg.value), j.callExpression(j.memberExpression(j.identifier('jest'), j.identifier('fn')), [])); }) : spyObjMethods.properties.map(function (arg) { var alreadyTransformed = arg.value.type === 'CallExpression' && arg.value.callee.type === 'MemberExpression' && arg.value.callee.object.name === 'jest' && arg.value.callee.property.name === 'fn'; return j.objectProperty(j.literal(arg.key.name), !alreadyTransformed ? j.callExpression(j.memberExpression(j.identifier('jest'), j.identifier('fn')), [j.arrowFunctionExpression([], arg.value)]) : arg.value); }); if (spyObjProperties !== undefined) { properties.push.apply(properties, __spreadArray([], __read((spyObjProperties.type === 'ArrayExpression' ? spyObjProperties.elements.map(function (arg) { return j.objectProperty(j.literal(arg.value), j.literal(null)); }) : spyObjProperties.properties.map(function (arg) { return j.objectProperty(j.literal(arg.key.name), arg.value); }))), false)); } if (typeParameters && path.parentPath.node.id) { path.parentPath.node.id.typeAnnotation = j.tsTypeAnnotation(j.tsTypeReference(j.tsQualifiedName(j.identifier('jest'), j.identifier('Mocked')), typeParameters)); } j(path).replaceWith(j.objectExpression(properties)); }); var isJasmineSpyType = function (typeAnnotation) { return typeAnnotation && typeAnnotation.type === 'TSTypeReference' && typeAnnotation.typeName.type === 'TSQualifiedName' && typeAnnotation.typeName.left.name === 'jasmine' && (typeAnnotation.typeName.right.name === 'Spy' || typeAnnotation.typeName.right.name === 'SpyObj'); }; var transformJasmineTypeToJest = function (type) { var _a; if (!type) return type; var typeArgument = (_a = type.typeParameters) === null || _a === void 0 ? void 0 : _a.params[0]; var jestType = type.typeName.right.name === 'Spy' ? 'Mock' : 'Mocked'; return j.tsTypeReference(j.tsQualifiedName(j.identifier('jest'), j.identifier(jestType)), typeArgument ? j.tsTypeParameterInstantiation([typeArgument]) : null); }; root .find(j.TSTypeReference) .filter(function (path) { return isJasmineSpyType(path.value); }) .forEach(function (path) { return path.replace(transformJasmineTypeToJest(path.value)); }); root .find(j.CallExpression, { callee: { type: 'MemberExpression', object: { type: 'CallExpression', callee: { type: 'Identifier', name: 'expect', }, }, property: { type: 'Identifier', name: function (name) { return name === 'toEqual' || name === 'toStrictEqual'; }, }, }, arguments: [ { type: 'CallExpression', callee: { type: 'MemberExpression', object: { type: 'Identifier', name: 'jasmine', }, property: { type: 'Identifier', name: 'arrayWithExactContents', }, }, }, ], }) .replaceWith(function (path) { var expectArgument = path.value.callee.object.arguments[0]; var jasmineArgument = path.value.arguments[0].arguments[0]; var methodName = path.value.callee.property.name; var newExpectArgument = j.callExpression(j.memberExpression(expectArgument, j.identifier('sort')), []); var newJasmineArgument = j.callExpression(j.memberExpression(jasmineArgument, j.identifier('sort')), []); return j.callExpression(j.memberExpression(j.callExpression(j.identifier('expect'), [newExpectArgument]), j.identifier(methodName)), [newJasmineArgument]); }); return (0, finale_1.default)(fileInfo, j, root, options); }