Skip to content

Commit c412dc7

Browse files
committed
🚨 Adds unit tests for CLI
1 parent 013d112 commit c412dc7

File tree

5 files changed

+348
-17
lines changed

5 files changed

+348
-17
lines changed

.changeset/tender-trains-train.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@codeshift/cli': patch
3+
---
4+
5+
Adds sequence flag

packages/cli/src/cli.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,22 @@ export interface Flags {
99
* Comma separated list of packages to run transforms for, @scope/package[@version]. If version is supplied, will only run transforms above that version
1010
*/
1111
packages?: string;
12+
/**
13+
* If the package flag is provided, runs all transforms from the provided version to the latest
14+
*/
15+
sequence?: boolean;
1216
/**
1317
* Parser to use for parsing the source files
1418
*/
15-
parser: 'babel' | 'babylon' | 'flow' | 'ts' | 'tsx';
19+
parser?: 'babel' | 'babylon' | 'flow' | 'ts' | 'tsx';
20+
/**
21+
* Transform files with these file extensions (comma separated list)
22+
*/
23+
extensions?: string;
24+
/**
25+
* Ignore files that match a provided glob expression
26+
*/
27+
ignorePattern?: string;
1628
}
1729

1830
export default meow(
@@ -22,17 +34,23 @@ Usage
2234
2335
Options
2436
--transform, -t the transform to run, will prompt for a transform if not provided and no module is passed
25-
--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
26-
--version, -v version number
37+
--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
38+
--sequence, -s, If the package flag is provided, runs all transforms from the provided version to the latest
2739
--parser, -p babel|babylon|flow|ts|tsx parser to use for parsing the source files (default: babel)
40+
--extensions, -e transform files with these file extensions (comma separated list) (default: js)
41+
--ignore-pattern, ignore files that match a provided glob expression
42+
--version, -v version number
2843
--help, 😱
2944
3045
Examples
46+
# Run a transform for "@mylib/button" version 3.0.0 only
47+
$ npx @codeshift/cli --packages @mylib/button@3.0.0 /project/src
48+
3149
# Run all transforms for "@mylib/button" greater than version 3.0.0 and @mylib/range greater than 4.0.0
32-
$ npx @codeshift/cli --packages @mylib/button@3.0.0,@mylib/range@4.0.0 /project/src
50+
$ npx @codeshift/cli --sequence --packages @mylib/button@3.0.0,@mylib/range@4.0.0 /project/src
3351
34-
# Run the "my-custom-transform" transform of the "button" package
35-
$ npx @codeshift/cli -t my-custom-transform /project/src
52+
# Run the "my-custom-transform" transform
53+
$ npx @codeshift/cli -t path/to/my-custom-transform /project/src
3654
3755
`,
3856
{
@@ -45,6 +63,10 @@ Examples
4563
type: 'string',
4664
alias: 'pkgs',
4765
},
66+
sequence: {
67+
type: 'boolean',
68+
alias: 's',
69+
},
4870
range: {
4971
type: 'string',
5072
alias: 'r',
@@ -53,7 +75,13 @@ Examples
5375
type: 'string',
5476
alias: 'p',
5577
},
56-
// TODO: Add `sequence` and `extensions` flag
78+
extensions: {
79+
type: 'string',
80+
alias: 'e',
81+
},
82+
ignorePattern: {
83+
type: 'string',
84+
},
5785
},
5886
},
5987
);

packages/cli/src/main.spec.ts

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
jest.mock('jscodeshift/src/Runner', () => ({
2+
run: jest.fn().mockImplementation(() => Promise.resolve()),
3+
}));
4+
5+
jest.mock('live-plugin-manager', () => ({
6+
PluginManager: () => ({
7+
install: () => Promise.resolve(undefined),
8+
getInfo: (name: string) =>
9+
Promise.resolve({ location: `node_modules/${name}` }),
10+
}),
11+
}));
12+
13+
jest.mock('fs-extra', () => ({
14+
readdir: () =>
15+
Promise.resolve([
16+
'18.0.0',
17+
'19.0.0',
18+
'20.0.0',
19+
'codeshift.config.js',
20+
'index.ts',
21+
]),
22+
}));
23+
24+
// @ts-ignore
25+
import * as jscodeshift from 'jscodeshift/src/Runner';
26+
27+
import main from './main';
28+
29+
const mockPath = 'src/pages/home-page/';
30+
31+
describe('main', () => {
32+
beforeEach(() => {
33+
jest.resetAllMocks();
34+
});
35+
36+
describe('when validating flags', () => {
37+
it('should exit early if file path is not supplied', async () => {
38+
expect.assertions(1);
39+
40+
try {
41+
await main([], { transform: 'path/to/transform.ts' });
42+
} catch (error) {
43+
expect(error.message).toMatch(
44+
'No path provided, please specify which files your codemod should modify',
45+
);
46+
}
47+
});
48+
49+
it('should exit early if nether a package or transform is supplied', async () => {
50+
expect.assertions(1);
51+
52+
try {
53+
await main([mockPath], {});
54+
} catch (error) {
55+
expect(error.message).toMatch(
56+
'No transform provided, please specify a transform with either the --transform or --packages flags',
57+
);
58+
}
59+
});
60+
});
61+
62+
describe('when running transforms with the -t flag', () => {
63+
const mockTransformPath = 'path/to/transform.ts';
64+
65+
it('should run transforms against multiple file paths', async () => {
66+
await main([mockPath, 'src/foo'], {
67+
transform: mockTransformPath,
68+
parser: 'babel',
69+
extensions: 'js',
70+
});
71+
72+
expect(jscodeshift.run).toHaveBeenCalledWith(
73+
mockTransformPath,
74+
expect.arrayContaining([mockPath, 'src/foo']),
75+
expect.objectContaining({
76+
parser: 'babel',
77+
extensions: 'js',
78+
}),
79+
);
80+
});
81+
82+
it('should transform js code', async () => {
83+
await main([mockPath], {
84+
transform: mockTransformPath,
85+
parser: 'babel',
86+
extensions: 'js,jsx',
87+
});
88+
89+
expect(jscodeshift.run).toHaveBeenCalledWith(
90+
mockTransformPath,
91+
expect.arrayContaining([mockPath]),
92+
expect.objectContaining({
93+
parser: 'babel',
94+
extensions: 'js,jsx',
95+
}),
96+
);
97+
});
98+
99+
it('should transform typescript code', async () => {
100+
await main([mockPath], {
101+
transform: mockTransformPath,
102+
parser: 'tsx',
103+
extensions: 'ts,tsx',
104+
});
105+
106+
expect(jscodeshift.run).toHaveBeenCalledWith(
107+
mockTransformPath,
108+
expect.arrayContaining([mockPath]),
109+
expect.objectContaining({
110+
parser: 'tsx',
111+
extensions: 'ts,tsx',
112+
}),
113+
);
114+
});
115+
116+
it('should transform flow code', async () => {
117+
await main([mockPath], {
118+
transform: mockTransformPath,
119+
parser: 'flow',
120+
extensions: 'js,jsx',
121+
});
122+
123+
expect(jscodeshift.run).toHaveBeenCalledWith(
124+
mockTransformPath,
125+
expect.arrayContaining([mockPath]),
126+
expect.objectContaining({
127+
parser: 'flow',
128+
extensions: 'js,jsx',
129+
}),
130+
);
131+
});
132+
});
133+
134+
describe('when running transforms with the -p flag', () => {
135+
it('should run package transform for single version', async () => {
136+
await main([mockPath], {
137+
packages: 'mylib@18.0.0',
138+
parser: 'babel',
139+
extensions: 'js',
140+
});
141+
142+
expect(jscodeshift.run).toHaveBeenCalledTimes(1);
143+
expect(jscodeshift.run).toHaveBeenCalledWith(
144+
'node_modules/@codeshift/mod-mylib/src/18.0.0/transform.ts',
145+
expect.arrayContaining([mockPath]),
146+
expect.objectContaining({
147+
parser: 'babel',
148+
extensions: 'js',
149+
}),
150+
);
151+
});
152+
153+
it('should run all package transforms for a package in sequence', async () => {
154+
await main([mockPath], {
155+
packages: 'mylib@18.0.0',
156+
parser: 'babel',
157+
extensions: 'js',
158+
sequence: true,
159+
});
160+
161+
expect(jscodeshift.run).toHaveBeenCalledTimes(3);
162+
expect(jscodeshift.run).toHaveBeenCalledWith(
163+
'node_modules/@codeshift/mod-mylib/src/18.0.0/transform.ts',
164+
expect.arrayContaining([mockPath]),
165+
expect.objectContaining({
166+
parser: 'babel',
167+
extensions: 'js',
168+
}),
169+
);
170+
expect(jscodeshift.run).toHaveBeenCalledWith(
171+
'node_modules/@codeshift/mod-mylib/src/19.0.0/transform.ts',
172+
expect.any(Array),
173+
expect.any(Object),
174+
);
175+
expect(jscodeshift.run).toHaveBeenCalledWith(
176+
'node_modules/@codeshift/mod-mylib/src/20.0.0/transform.ts',
177+
expect.any(Array),
178+
expect.any(Object),
179+
);
180+
});
181+
182+
it('should run scoped package transforms', async () => {
183+
await main([mockPath], {
184+
packages: '@myscope/mylib@19.0.0',
185+
parser: 'babel',
186+
extensions: 'js',
187+
});
188+
189+
expect(jscodeshift.run).toHaveBeenCalledWith(
190+
'node_modules/@codeshift/mod-myscope__mylib/src/19.0.0/transform.ts',
191+
expect.any(Array),
192+
expect.any(Object),
193+
);
194+
});
195+
196+
it('should run multiple package transforms', async () => {
197+
await main([mockPath], {
198+
packages: 'mylib@20.0.0,myotherlib@20.0.0',
199+
parser: 'babel',
200+
extensions: 'js',
201+
});
202+
203+
expect(jscodeshift.run).toHaveBeenCalledTimes(2);
204+
expect(jscodeshift.run).toHaveBeenCalledWith(
205+
'node_modules/@codeshift/mod-mylib/src/20.0.0/transform.ts',
206+
expect.any(Array),
207+
expect.any(Object),
208+
);
209+
expect(jscodeshift.run).toHaveBeenCalledWith(
210+
'node_modules/@codeshift/mod-myotherlib/src/20.0.0/transform.ts',
211+
expect.any(Array),
212+
expect.any(Object),
213+
);
214+
});
215+
216+
it('should run multiple scoped package transforms', async () => {
217+
await main([mockPath], {
218+
packages: '@myscope/mylib@20.0.0,@myotherscope/myotherlib@20.0.0',
219+
parser: 'babel',
220+
extensions: 'js',
221+
});
222+
223+
expect(jscodeshift.run).toHaveBeenCalledTimes(2);
224+
expect(jscodeshift.run).toHaveBeenCalledWith(
225+
'node_modules/@codeshift/mod-myscope__mylib/src/20.0.0/transform.ts',
226+
expect.any(Array),
227+
expect.any(Object),
228+
);
229+
expect(jscodeshift.run).toHaveBeenCalledWith(
230+
'node_modules/@codeshift/mod-myotherscope__myotherlib/src/20.0.0/transform.ts',
231+
expect.any(Array),
232+
expect.any(Object),
233+
);
234+
});
235+
236+
it('should handle empty package transforms', async () => {
237+
await main([mockPath], {
238+
packages: 'mylib@20.0.0,,,',
239+
parser: 'babel',
240+
extensions: 'js',
241+
});
242+
243+
expect(jscodeshift.run).toHaveBeenCalledTimes(1);
244+
expect(jscodeshift.run).toHaveBeenCalledWith(
245+
'node_modules/@codeshift/mod-mylib/src/20.0.0/transform.ts',
246+
expect.any(Array),
247+
expect.any(Object),
248+
);
249+
});
250+
251+
it('should throw when package format is invalid', async () => {
252+
expect.assertions(1);
253+
254+
try {
255+
await main([mockPath], {
256+
packages: 'mylib@NOT_SEMVER',
257+
parser: 'babel',
258+
extensions: 'js',
259+
});
260+
} catch (error) {
261+
expect(error.message).toMatch(
262+
'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',
263+
);
264+
}
265+
});
266+
267+
it('should run both transforms and package transforms', async () => {
268+
await main([mockPath], {
269+
packages: 'mylib@20.0.0',
270+
transform: 'path/to/transform.ts',
271+
parser: 'babel',
272+
extensions: 'js',
273+
});
274+
275+
expect(jscodeshift.run).toHaveBeenCalledTimes(2);
276+
expect(jscodeshift.run).toHaveBeenCalledWith(
277+
'node_modules/@codeshift/mod-mylib/src/20.0.0/transform.ts',
278+
expect.arrayContaining([mockPath]),
279+
expect.objectContaining({
280+
parser: 'babel',
281+
extensions: 'js',
282+
}),
283+
);
284+
expect(jscodeshift.run).toHaveBeenCalledWith(
285+
'path/to/transform.ts',
286+
expect.any(Array),
287+
expect.any(Object),
288+
);
289+
});
290+
});
291+
});

0 commit comments

Comments
 (0)