diff --git a/src/transformers/ava.test.ts b/src/transformers/ava.test.ts index 5d9a314c..7240843f 100644 --- a/src/transformers/ava.test.ts +++ b/src/transformers/ava.test.ts @@ -328,6 +328,71 @@ test('my test', () => { ) }) +test('supports other test names', () => { + expectTransformation( + ` +import test from 'ava'; + +test('my test', async (test) => { + test.is('msg', 'other msg'); + const deeper = () => { + test.is('deeper', 'even deeper'); + }; + const willNotChange = (test) => { + test.is('notChanged', 'notChanged'); + }; + const alsoNoChange = () => { + const test = {}; + test.is('notChanged', 'notChanged'); + } +}); + +test('another test', async (x) => { + x.is('msg', 'other msg'); + const deeper = () => { + x.is('deeper', 'even deeper'); + }; + const willNotChange = (x) => { + x.is('notChanged', 'notChanged'); + }; + const alsoNoChange = () => { + const x = {}; + x.is('notChanged', 'notChanged'); + } +}); +`, + ` +test('my test', async () => { + expect('msg').toBe('other msg'); + const deeper = () => { + expect('deeper').toBe('even deeper'); + }; + const willNotChange = (test) => { + test.is('notChanged', 'notChanged'); + }; + const alsoNoChange = () => { + const test = {}; + test.is('notChanged', 'notChanged'); + } +}); + +test('another test', async () => { + expect('msg').toBe('other msg'); + const deeper = () => { + expect('deeper').toBe('even deeper'); + }; + const willNotChange = (x) => { + x.is('notChanged', 'notChanged'); + }; + const alsoNoChange = () => { + const x = {}; + x.is('notChanged', 'notChanged'); + } +}); +` + ) +}) + test('converts test.todo', () => { expectTransformation( ` @@ -381,18 +446,6 @@ test('not supported warnings: unmapped t property', () => { ]) }) -test('not supported warnings: non standard argument for test', () => { - wrappedPlugin(` - import test from 'ava'; - test(x => { - x.equal(1, 1); - }); - `) - expect(consoleWarnings).toEqual([ - 'jest-codemods warning: (test.js line 3) Argument to test function should be named "t" not "x"', - ]) -}) - test('warns about some conflicting packages', () => { wrappedPlugin(` import ava from 'ava'; @@ -438,3 +491,43 @@ test(() => {}); ` ) }) + +test('can handle after.always or afterEach.always', () => { + expectTransformation( + ` +import test from 'ava'; + +test.after.always(t => {}); +test.afterEach.always(t => {}); +`, + ` +afterAll(() => {}); +afterEach(() => {}); +` + ) +}) + +test('does not mess with the context', () => { + expectTransformation( + ` +import test from 'ava'; + +test.beforeEach((test) => { + test.context.hello = () => console.log('hello'); +}); + +test('uses context', test => { + test.context.hello(); +}); +`, + ` +beforeEach(() => { + test.context.hello = () => console.log('hello'); +}); + +test('uses context', () => { + context.hello(); +}); +` + ) +}) diff --git a/src/transformers/ava.ts b/src/transformers/ava.ts index 10184582..1c630e68 100644 --- a/src/transformers/ava.ts +++ b/src/transformers/ava.ts @@ -2,6 +2,7 @@ * Codemod for transforming AVA tests into Jest. */ import * as jscodeshift from 'jscodeshift' +import { Identifier, MemberExpression } from 'jscodeshift' import { PROP_WITH_SECONDS_ARGS } from '../utils/consts' import finale from '../utils/finale' @@ -12,7 +13,7 @@ import { getMemberExpressionElements, } from '../utils/recast-helpers' import { - detectUnsupportedNaming, + renameExecutionInterface, rewriteAssertionsAndTestArgument, rewriteDestructuredTArgument, } from '../utils/tape-ava-helpers' @@ -71,7 +72,7 @@ const avaToJest: jscodeshift.Transform = (fileInfo, api, options) => { const transforms = [ () => rewriteDestructuredTArgument(fileInfo, j, ast, testFunctionName), - () => detectUnsupportedNaming(fileInfo, j, ast, testFunctionName), + () => renameExecutionInterface(fileInfo, j, ast, testFunctionName), function updateAssertions() { ast .find(j.CallExpression, { @@ -164,12 +165,31 @@ const avaToJest: jscodeshift.Transform = (fileInfo, api, options) => { }) function mapPathToJestCallExpression(p) { + let { scope } = p + + const { + node: { + callee: { + object: { name }, + }, + }, + } = p + + while (scope) { + if (scope.declares(name)) { + return j.callExpression( + j.memberExpression(p.node.callee.object, p.node.callee.property), + p.node.arguments + ) + } + scope = scope.parent + } let jestMethod = 'test' const jestMethodArgs = p.node.arguments - // List like ['test', 'serial', 'cb'] + // List like ['test', 'serial', 'cb', 'always'] const avaMethods = getMemberExpressionElements(p.node.callee).filter( - (e) => e !== 'serial' && e !== testFunctionName && e !== 'cb' + (e) => e !== 'serial' && e !== testFunctionName && e !== 'cb' && e != 'always' ) if (avaMethods.length === 1) { @@ -180,6 +200,12 @@ const avaToJest: jscodeshift.Transform = (fileInfo, api, options) => { jestMethod = avaMethod logWarning(`Unknown AVA method "${avaMethod}"`, p) } + } else if (avaMethods[0] === 'context') { + let identifier: Identifier | MemberExpression = j.identifier(avaMethods[0]) + avaMethods.slice(1).forEach((next) => { + identifier = j.memberExpression(identifier, j.identifier(next)) + }) + return j.callExpression(identifier, jestMethodArgs) } else if (avaMethods.length > 0) { logWarning('Skipping setup/teardown hooks is currently not supported', p) } diff --git a/src/transformers/jasmine-this.ts b/src/transformers/jasmine-this.ts index 8b67770d..59036b0d 100644 --- a/src/transformers/jasmine-this.ts +++ b/src/transformers/jasmine-this.ts @@ -1,9 +1,9 @@ /** * Codemod for transforming Jasmine `this` context into Jest v20+ compatible syntax. */ +import { NodePath } from 'ast-types' import * as jscodeshift from 'jscodeshift' import { Collection } from 'jscodeshift/src/Collection' -import { NodePath } from 'recast' import finale from '../utils/finale' diff --git a/src/utils/tape-ava-helpers.ts b/src/utils/tape-ava-helpers.ts index df47cabd..b7724dbd 100644 --- a/src/utils/tape-ava-helpers.ts +++ b/src/utils/tape-ava-helpers.ts @@ -4,9 +4,13 @@ import logger from './logger' * Rewrite last argument of a given CallExpression path * @param {jscodeshift} j * @param {CallExpression} path + * @param {string} newArgument */ function renameTestFunctionArgument(j, path, newArgument) { const lastArg = path.node.arguments[path.node.arguments.length - 1] + if (!lastArg) { + return + } if (lastArg.type === 'ArrowFunctionExpression') { const arrowFunction = j.arrowFunctionExpression( [j.identifier(newArgument === '' ? '()' : newArgument)], @@ -163,6 +167,54 @@ export function rewriteDestructuredTArgument(fileInfo, j, ast, testFunctionName) }) } +/** + * Rewrite Execution reference name if not 't' + * + * @param fileInfo + * @param {jscodeshift} j + * @param {Collection} ast + * @param {string} testFunctionName + */ +export function renameExecutionInterface(fileInfo, j, ast, testFunctionName) { + ast + .find(j.CallExpression, { + callee: (callee) => + callee.name === testFunctionName || + (callee.object && callee.object.name === testFunctionName), + }) + .forEach((p) => { + const lastArg = p.value.arguments[p.value.arguments.length - 1] + if (lastArg?.params?.[0]) { + const lastArgName = lastArg.params[0].name + if (lastArgName === 't') { + return + } + j(p) + .find(j.Identifier, { + name: lastArgName, + }) + .filter((path) => path.parent.node === lastArg) + .forEach((path) => { + path.get('name').replace('t') + const rootScope = path.scope + j(p) + .find(j.CallExpression, { callee: { object: { name: lastArgName } } }) + .forEach((path) => { + let { scope } = path + while (scope && scope !== rootScope) { + if (scope.declares(lastArgName)) { + return + } + scope = scope.parent + } + + path.node.callee.object.name = 't' + }) + }) + } + }) +} + /** * Validated that "t" is the test argument name. *