Skip to content

Codemods for transitioning from tap/tape/mocha #644

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
sindresorhus opened this issue Mar 15, 2016 · 18 comments
Closed

Codemods for transitioning from tap/tape/mocha #644

sindresorhus opened this issue Mar 15, 2016 · 18 comments
Labels

Comments

@sindresorhus
Copy link
Member

Using jscodeshift.

Codemods allow you to transform your code to make breaking changes but without breaking the code. Codemods take a JS file as input and turn them into Abstract Syntax Trees (AST) and apply transformations on this AST later converting them back to JS again.

It would pretty much let users transform their existing tap/tape/mocha tests to AVA with minimal manual changes. From tap/tape would be the easiest as they have very similar syntax and semantics. This would result in an incredible onboarding experience! 🦄

Let me know if anyone would be interested in working on codemods for transitioning from either tap, tape, or mocha. I would be happy to mentor about the differences between tap/tape/mocha and AVA.

Resources

From #178.

@vadimdemedes
Copy link
Contributor

Love this idea! Would be super cool if someone could post links to great medium-sized projects using tap/tape/mocha to test these codemods against :)

@spudly
Copy link
Contributor

spudly commented Mar 31, 2016

Here's a start for converting mocha -> ava. Certainly not comprehensive but it works for my current needs. Someone can start with this and build it up.

All it does for now is rename it, before, beforeEach, after, and afterEach to the ava equivalents.

Also adds the t parameter for the it/test calls.

https://astexplorer.net/#/Uwl4f415pa

@vadimdemedes
Copy link
Contributor

Would also be nice to convert mocha's:

describe('group', () => {
  it('test');
});

into:

test('group - test');

@spudly
Copy link
Contributor

spudly commented Mar 31, 2016

@vdemedes,

Like I said, this is just a start. Changing describe blocks can get hairy because in mocha they might have their own set of lifecycle hooks (beforeEach, etc.)

I probably don't have time to do a full codemod, or else I would have opened a pull request. Just thought it might be useful to people until a full one is implemented.

@vadimdemedes
Copy link
Contributor

@spudly Of course, just dumping thoughts ;) Thanks, definitely helpful!

@spudly
Copy link
Contributor

spudly commented Apr 1, 2016

For completeness, here's a list of everything that I think would be needed for a complete mocha codemod:

  • transform it() to test()
  • add t parameter to a ArrowFunctionExpression passed to test()
  • don't de-sync async functions (the codemod above will mess those up currently)
  • add t parameter to a FunctionExpression passed to test()
  • transform beforeEach() to test.beforeEach()
  • transform afterEach() to test.afterEach()
  • transform after() to test.after()
  • transform before() to test.before()
  • remove describe() blocks, prepending their text to each test block's description
  • intelligently handle nested describe blocks if possible. how do we handle nested describe blocks with their own lifecycle hooks?
  • if the test body FunctionExpression or ArrowFunctionExpression is async, it should use test.serial instead of test

Also, it would be nice to have a separate codemod to transform chai assertions to ava assertions.

@jfmengels
Copy link
Contributor

Adding to the list

  • Transform tests containing a callback ( it('foo', function (done) { ... }) ) to test.cb(t => ... t.end();

👍 on the separate codemod for chai assertions.

@jamestalmage
Copy link
Contributor

how do we handle nested describe blocks with their own lifecycle hooks

Short of implementing #222, I am not sure. We could try converting them to setup functions:

describe('outer', function () {
  beforeEach(function() {
     // outer beforeEach
  });

  it('outerTest', function () {
    // outer test
  }

  describe('inner', function () {
    beforeEach(function () {
      // inner beforeEach
    });

    it('innerTest', function() {
      // inner test
    });
  });
});

converts to:

function outerBeforeEach() {
  // outer beforeEach
}

function innerBeforeEach() {
  // inner beforeEach
} 

test('outerTest', t => {
  outerBeforeEach();
  // outer test
}); 

test('innerTest', t => {
  outerBeforeEach();
  innerBeforeEach();
  // inner test
}); 

Honestly though - it seems so complicated, it might just be better to implement grouping in AVA if we really want to make the transition easy.

@jamestalmage
Copy link
Contributor

A big problem will be handling variables declared / setup in beforeEach, etc.

describe('group', function () {
  var fn, spy;

  beforeEach(function () {
    spy = sinon.spy();
    fn = proxyquire('some-module', {
      'some-dependency': spy
    });
  });

  it('test1', function () {
     fn(someArgs);
     assert(spy.calledWith(...));
  });

  it('test2', function () {
     fn(someArgs);
     assert(spy.calledWith(...));
  });
});

I think maybe the best solution might be to just use .serial mode across the board (certainly if beforeEach is detected anywhere in the file). Users can selectively convert tests to concurrent mode as they gain an understanding of what is happening.

@jfmengels
Copy link
Contributor

For nested describes, or too many side-by-side describes, we could maybe split tests up into multiple files. I know jscodeshift doesn't allow to create new files or update files other than the currently evaluated one, but what we can do is split them up into clearly separated parts in the same file, so that the user can easily cut-paste each section in a new file.

Using your previous input example

<<<<<< innerTest.js

test.beforeEach(...); // outer beforeEach

test.beforeEach(...); // inner beforeEach

test('innerTest', t => {
});

<<<<<< outerTest.js

test.beforeEach(...); // outer beforeEach

test('outerTest', t => {
});

We could then create a tool that applies the jscodemod, then reads the updated files, creates new files and cut-paste the code inside the appropriate file. We don't have to do a 100% jscodeshift transformation tool.

@jamestalmage
Copy link
Contributor

I know jscodeshift doesn't allow to create new files or update files other than the currently evaluated one

Why not?

@jfmengels
Copy link
Contributor

I know jscodeshift doesn't allow to create new files or update files other than the currently evaluated one

Why not?

Well, it's a simple AST transform tool, but you're right that we don't have to play "by-the-rules", and we could call fs methods from there. Might not be a bad idea

@spudly
Copy link
Contributor

spudly commented Apr 1, 2016

Even splitting into multiple files will get hairy, because nested lifecycle hooks get merged by mocha. For example,

describe('a', () => {
  beforeEach(setupA);
  describe('b', () => {
    beforeEach(setupB);
    it('testA', testA);
    it('testB', testB);
    it('testC', testC);
  });
});

This will execute functions in the following order

  • setupA
  • setupB
  • testA
  • setupA
  • setupB
  • testB
  • setupA
  • setupB
  • testC

@jfmengels
Copy link
Contributor

@spudly Isn't that the same thing as?

test.beforeEach(setupA);
test.beforeEach(setupB);
test(testA);
test(testB);
test(testC);

@spudly
Copy link
Contributor

spudly commented Apr 2, 2016

Yes, I suppose you're right. We'd just have to make sure all applicable hooks are included in each sub-file.

@jamestalmage
Copy link
Contributor

The way the jscodemod plugins are specified, you could use recast directly for complex transforms like this, and continue to use the simpler jscodemod API for stuff like t.same => t.deepEqual.

@jamestalmage
Copy link
Contributor

jamestalmage/ava-codemods created.

Everyone in the conversation so far is added as a collaborator.

@jamestalmage
Copy link
Contributor

I have created issues for each of the different conversions being discussed in the other repo:

Let's continue conversation over there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants