Skip to content

Commit

Permalink
🚨 Adds unit tests for CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
danieldelcore committed May 19, 2021
1 parent 013d112 commit c412dc7
Show file tree
Hide file tree
Showing 5 changed files with 348 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .changeset/tender-trains-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@codeshift/cli': patch
---

Adds sequence flag
42 changes: 35 additions & 7 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,22 @@ export interface Flags {
* Comma separated list of packages to run transforms for, @scope/package[@version]. If version is supplied, will only run transforms above that version
*/
packages?: string;
/**
* If the package flag is provided, runs all transforms from the provided version to the latest
*/
sequence?: boolean;
/**
* Parser to use for parsing the source files
*/
parser: 'babel' | 'babylon' | 'flow' | 'ts' | 'tsx';
parser?: 'babel' | 'babylon' | 'flow' | 'ts' | 'tsx';
/**
* Transform files with these file extensions (comma separated list)
*/
extensions?: string;
/**
* Ignore files that match a provided glob expression
*/
ignorePattern?: string;
}

export default meow(
Expand All @@ -22,17 +34,23 @@ Usage
Options
--transform, -t the transform to run, will prompt for a transform if not provided and no module is passed
--packages -pkgs, Comma separated list of packages to run transforms for, @scope/package[@version]. If version is supplied, will only run transforms for that version and above
--version, -v version number
--packages, -pkgs, Comma separated list of packages to run transforms for, @scope/package[@version]. If version is supplied, will only run transforms for that version and above
--sequence, -s, If the package flag is provided, runs all transforms from the provided version to the latest
--parser, -p babel|babylon|flow|ts|tsx parser to use for parsing the source files (default: babel)
--extensions, -e transform files with these file extensions (comma separated list) (default: js)
--ignore-pattern, ignore files that match a provided glob expression
--version, -v version number
--help, 😱
Examples
# Run a transform for "@mylib/button" version 3.0.0 only
$ npx @codeshift/cli --packages @mylib/button@3.0.0 /project/src
# Run all transforms for "@mylib/button" greater than version 3.0.0 and @mylib/range greater than 4.0.0
$ npx @codeshift/cli --packages @mylib/button@3.0.0,@mylib/range@4.0.0 /project/src
$ npx @codeshift/cli --sequence --packages @mylib/button@3.0.0,@mylib/range@4.0.0 /project/src
# Run the "my-custom-transform" transform of the "button" package
$ npx @codeshift/cli -t my-custom-transform /project/src
# Run the "my-custom-transform" transform
$ npx @codeshift/cli -t path/to/my-custom-transform /project/src
`,
{
Expand All @@ -45,6 +63,10 @@ Examples
type: 'string',
alias: 'pkgs',
},
sequence: {
type: 'boolean',
alias: 's',
},
range: {
type: 'string',
alias: 'r',
Expand All @@ -53,7 +75,13 @@ Examples
type: 'string',
alias: 'p',
},
// TODO: Add `sequence` and `extensions` flag
extensions: {
type: 'string',
alias: 'e',
},
ignorePattern: {
type: 'string',
},
},
},
);
291 changes: 291 additions & 0 deletions packages/cli/src/main.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
jest.mock('jscodeshift/src/Runner', () => ({
run: jest.fn().mockImplementation(() => Promise.resolve()),
}));

jest.mock('live-plugin-manager', () => ({
PluginManager: () => ({
install: () => Promise.resolve(undefined),
getInfo: (name: string) =>
Promise.resolve({ location: `node_modules/${name}` }),
}),
}));

jest.mock('fs-extra', () => ({
readdir: () =>
Promise.resolve([
'18.0.0',
'19.0.0',
'20.0.0',
'codeshift.config.js',
'index.ts',
]),
}));

// @ts-ignore
import * as jscodeshift from 'jscodeshift/src/Runner';

import main from './main';

const mockPath = 'src/pages/home-page/';

describe('main', () => {
beforeEach(() => {
jest.resetAllMocks();
});

describe('when validating flags', () => {
it('should exit early if file path is not supplied', async () => {
expect.assertions(1);

try {
await main([], { transform: 'path/to/transform.ts' });
} catch (error) {
expect(error.message).toMatch(
'No path provided, please specify which files your codemod should modify',
);
}
});

it('should exit early if nether a package or transform is supplied', async () => {
expect.assertions(1);

try {
await main([mockPath], {});
} catch (error) {
expect(error.message).toMatch(
'No transform provided, please specify a transform with either the --transform or --packages flags',
);
}
});
});

describe('when running transforms with the -t flag', () => {
const mockTransformPath = 'path/to/transform.ts';

it('should run transforms against multiple file paths', async () => {
await main([mockPath, 'src/foo'], {
transform: mockTransformPath,
parser: 'babel',
extensions: 'js',
});

expect(jscodeshift.run).toHaveBeenCalledWith(
mockTransformPath,
expect.arrayContaining([mockPath, 'src/foo']),
expect.objectContaining({
parser: 'babel',
extensions: 'js',
}),
);
});

it('should transform js code', async () => {
await main([mockPath], {
transform: mockTransformPath,
parser: 'babel',
extensions: 'js,jsx',
});

expect(jscodeshift.run).toHaveBeenCalledWith(
mockTransformPath,
expect.arrayContaining([mockPath]),
expect.objectContaining({
parser: 'babel',
extensions: 'js,jsx',
}),
);
});

it('should transform typescript code', async () => {
await main([mockPath], {
transform: mockTransformPath,
parser: 'tsx',
extensions: 'ts,tsx',
});

expect(jscodeshift.run).toHaveBeenCalledWith(
mockTransformPath,
expect.arrayContaining([mockPath]),
expect.objectContaining({
parser: 'tsx',
extensions: 'ts,tsx',
}),
);
});

it('should transform flow code', async () => {
await main([mockPath], {
transform: mockTransformPath,
parser: 'flow',
extensions: 'js,jsx',
});

expect(jscodeshift.run).toHaveBeenCalledWith(
mockTransformPath,
expect.arrayContaining([mockPath]),
expect.objectContaining({
parser: 'flow',
extensions: 'js,jsx',
}),
);
});
});

describe('when running transforms with the -p flag', () => {
it('should run package transform for single version', async () => {
await main([mockPath], {
packages: 'mylib@18.0.0',
parser: 'babel',
extensions: 'js',
});

expect(jscodeshift.run).toHaveBeenCalledTimes(1);
expect(jscodeshift.run).toHaveBeenCalledWith(
'node_modules/@codeshift/mod-mylib/src/18.0.0/transform.ts',
expect.arrayContaining([mockPath]),
expect.objectContaining({
parser: 'babel',
extensions: 'js',
}),
);
});

it('should run all package transforms for a package in sequence', async () => {
await main([mockPath], {
packages: 'mylib@18.0.0',
parser: 'babel',
extensions: 'js',
sequence: true,
});

expect(jscodeshift.run).toHaveBeenCalledTimes(3);
expect(jscodeshift.run).toHaveBeenCalledWith(
'node_modules/@codeshift/mod-mylib/src/18.0.0/transform.ts',
expect.arrayContaining([mockPath]),
expect.objectContaining({
parser: 'babel',
extensions: 'js',
}),
);
expect(jscodeshift.run).toHaveBeenCalledWith(
'node_modules/@codeshift/mod-mylib/src/19.0.0/transform.ts',
expect.any(Array),
expect.any(Object),
);
expect(jscodeshift.run).toHaveBeenCalledWith(
'node_modules/@codeshift/mod-mylib/src/20.0.0/transform.ts',
expect.any(Array),
expect.any(Object),
);
});

it('should run scoped package transforms', async () => {
await main([mockPath], {
packages: '@myscope/mylib@19.0.0',
parser: 'babel',
extensions: 'js',
});

expect(jscodeshift.run).toHaveBeenCalledWith(
'node_modules/@codeshift/mod-myscope__mylib/src/19.0.0/transform.ts',
expect.any(Array),
expect.any(Object),
);
});

it('should run multiple package transforms', async () => {
await main([mockPath], {
packages: 'mylib@20.0.0,myotherlib@20.0.0',
parser: 'babel',
extensions: 'js',
});

expect(jscodeshift.run).toHaveBeenCalledTimes(2);
expect(jscodeshift.run).toHaveBeenCalledWith(
'node_modules/@codeshift/mod-mylib/src/20.0.0/transform.ts',
expect.any(Array),
expect.any(Object),
);
expect(jscodeshift.run).toHaveBeenCalledWith(
'node_modules/@codeshift/mod-myotherlib/src/20.0.0/transform.ts',
expect.any(Array),
expect.any(Object),
);
});

it('should run multiple scoped package transforms', async () => {
await main([mockPath], {
packages: '@myscope/mylib@20.0.0,@myotherscope/myotherlib@20.0.0',
parser: 'babel',
extensions: 'js',
});

expect(jscodeshift.run).toHaveBeenCalledTimes(2);
expect(jscodeshift.run).toHaveBeenCalledWith(
'node_modules/@codeshift/mod-myscope__mylib/src/20.0.0/transform.ts',
expect.any(Array),
expect.any(Object),
);
expect(jscodeshift.run).toHaveBeenCalledWith(
'node_modules/@codeshift/mod-myotherscope__myotherlib/src/20.0.0/transform.ts',
expect.any(Array),
expect.any(Object),
);
});

it('should handle empty package transforms', async () => {
await main([mockPath], {
packages: 'mylib@20.0.0,,,',
parser: 'babel',
extensions: 'js',
});

expect(jscodeshift.run).toHaveBeenCalledTimes(1);
expect(jscodeshift.run).toHaveBeenCalledWith(
'node_modules/@codeshift/mod-mylib/src/20.0.0/transform.ts',
expect.any(Array),
expect.any(Object),
);
});

it('should throw when package format is invalid', async () => {
expect.assertions(1);

try {
await main([mockPath], {
packages: 'mylib@NOT_SEMVER',
parser: 'babel',
extensions: 'js',
});
} catch (error) {
expect(error.message).toMatch(
'Invalid version provided to the --packages flag. Package mylib@NOT_SEMVER is missing version. Please try: "@[scope]/[package]@[version]" for example @mylib/avatar@10.0.0',
);
}
});

it('should run both transforms and package transforms', async () => {
await main([mockPath], {
packages: 'mylib@20.0.0',
transform: 'path/to/transform.ts',
parser: 'babel',
extensions: 'js',
});

expect(jscodeshift.run).toHaveBeenCalledTimes(2);
expect(jscodeshift.run).toHaveBeenCalledWith(
'node_modules/@codeshift/mod-mylib/src/20.0.0/transform.ts',
expect.arrayContaining([mockPath]),
expect.objectContaining({
parser: 'babel',
extensions: 'js',
}),
);
expect(jscodeshift.run).toHaveBeenCalledWith(
'path/to/transform.ts',
expect.any(Array),
expect.any(Object),
);
});
});
});
Loading

0 comments on commit c412dc7

Please sign in to comment.