Repository: skovhus/jest-codemods
Pull request #226 (2021-08-11 21:33:17)

Allow names other than t for ava execution context
Updates to:

  * ava parser

In my company we often use the following, and I would much rather update this lib than fix all the tests by hand.
```typescript
import test from 'ava';

test('some test', (test) => {
  ...
});
```


Patch:
diff --git a/src/transformers/ava.ts b/src/transformers/ava.ts
index 1018458..1c630e6 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 8b67770..59036b0 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 df47cab..b7724db 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.
  *


Test patch:
diff --git a/src/transformers/ava.test.ts b/src/transformers/ava.test.ts
index 5d9a314..7240843 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();
+});
+`
+  )
+})