Skip to content

Commit b0b2b60

Browse files
bjohansebaskjugi
andauthored
test: add test for cli (#6)
* test: initial test for interative mode * ci: build on windows and macos * Improve name test * deps: remove execa * Add build directory to tsconfig * update options param types and add console.info for exit reason * delete build from unit test CI * update transform unit test with mocks * revert change in ci.yml file for github workflow * prevent redundant return from transform method and adjust index file * update gitignore * update transform list config * update tsconfig and exclude all tests files * exclude build dir * update jest config file to ignore build directory --------- Co-authored-by: Filip Kudła <[email protected]>
1 parent f08e3a1 commit b0b2b60

File tree

9 files changed

+160
-48
lines changed

9 files changed

+160
-48
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ jobs:
2727
uses: actions/setup-node@v4
2828
with:
2929
node-version: 'lts/*'
30-
persist-credentials: false
3130

3231
- name: Install dependencies
3332
run: npm install --ignore-scripts --only=dev
@@ -39,11 +38,11 @@ jobs:
3938
strategy:
4039
fail-fast: false
4140
matrix:
42-
os: [ubuntu-latest]
41+
os: [ubuntu-latest, windows-latest, macos-latest]
4342
node-version: [18, 19, 20, 21, 22, 23]
4443
# Node.js release schedule: https://nodejs.org/en/about/releases/
4544

46-
name: Node.js ${{ matrix.node-version }}
45+
name: Node.js ${{ matrix.node-version }} - ${{matrix.os}}
4746

4847
runs-on: ${{ matrix.os }}
4948
steps:

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ node_modules
1212
coverage
1313

1414
# Build Outputs
15-
dist
15+
build
1616

1717
# Debug
1818
npm-debug.log*

commands/__test__/transform.spec.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { join } from 'node:path'
2+
import { run } from 'jscodeshift/src/Runner'
3+
import prompts from 'prompts'
4+
import { transform } from '../transform'
5+
6+
jest.mock('jscodeshift/src/Runner', () => ({
7+
run: jest.fn(),
8+
}))
9+
10+
describe('interactive mode', () => {
11+
beforeEach(() => {
12+
jest.clearAllMocks()
13+
})
14+
15+
it('runs without codemodName and source params provided', async () => {
16+
const spyOnConsole = jest.spyOn(console, 'log').mockImplementation()
17+
18+
prompts.inject(['magic-redirect'])
19+
prompts.inject(['./transforms/__testfixtures__'])
20+
21+
await transform(undefined, undefined, { dry: true, silent: true })
22+
23+
expect(spyOnConsole).not.toHaveBeenCalled()
24+
expect(run).toHaveBeenCalledTimes(1)
25+
expect(run).toHaveBeenCalledWith(
26+
join(__dirname, '../../', 'transforms/magic-redirect.js'),
27+
['./transforms/__testfixtures__'],
28+
{
29+
babel: false,
30+
dry: true,
31+
extensions: 'cts,mts,ts,js,mjs,cjs',
32+
ignorePattern: '**/node_modules/**',
33+
silent: true,
34+
verbose: 0,
35+
},
36+
)
37+
})
38+
39+
it('runs properly on incorrect user input', async () => {
40+
const spyOnConsole = jest.spyOn(console, 'log').mockImplementation()
41+
42+
prompts.inject(['magic-redirect'])
43+
44+
await transform('bad-codemod', './transforms/__testfixtures__', {
45+
dry: true,
46+
silent: true,
47+
})
48+
49+
expect(spyOnConsole).not.toHaveBeenCalled()
50+
expect(run).toHaveBeenCalledTimes(1)
51+
expect(run).toHaveBeenCalledWith(
52+
join(__dirname, '../../', 'transforms/magic-redirect.js'),
53+
['./transforms/__testfixtures__'],
54+
{
55+
babel: false,
56+
dry: true,
57+
extensions: 'cts,mts,ts,js,mjs,cjs',
58+
ignorePattern: '**/node_modules/**',
59+
silent: true,
60+
verbose: 0,
61+
},
62+
)
63+
})
64+
65+
it('runs with codemodName and without source param provided', async () => {
66+
const spyOnConsole = jest.spyOn(console, 'log').mockImplementation()
67+
68+
prompts.inject(['__testfixtures__'])
69+
70+
await transform('magic-redirect', undefined, {
71+
dry: true,
72+
silent: true,
73+
})
74+
75+
expect(spyOnConsole).not.toHaveBeenCalled()
76+
expect(run).toHaveBeenCalledTimes(1)
77+
expect(run).toHaveBeenCalledWith(join(__dirname, '../../', 'transforms/magic-redirect.js'), ['__testfixtures__'], {
78+
babel: false,
79+
dry: true,
80+
extensions: 'cts,mts,ts,js,mjs,cjs',
81+
ignorePattern: '**/node_modules/**',
82+
silent: true,
83+
verbose: 0,
84+
})
85+
})
86+
})
87+
88+
describe('Non-Interactive Mode', () => {
89+
beforeEach(() => {
90+
jest.clearAllMocks()
91+
})
92+
93+
it('Transforms code with codemodName and source params provided', async () => {
94+
const spyOnConsole = jest.spyOn(console, 'log').mockImplementation()
95+
96+
await transform('magic-redirect', '__testfixtures__', {
97+
dry: true,
98+
silent: true,
99+
})
100+
101+
expect(spyOnConsole).not.toHaveBeenCalled()
102+
expect(run).toHaveBeenCalledTimes(1)
103+
expect(run).toHaveBeenCalledWith(join(__dirname, '../../', 'transforms/magic-redirect.js'), ['__testfixtures__'], {
104+
babel: false,
105+
dry: true,
106+
extensions: 'cts,mts,ts,js,mjs,cjs',
107+
ignorePattern: '**/node_modules/**',
108+
silent: true,
109+
verbose: 0,
110+
})
111+
})
112+
})

commands/transform.ts

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
11
import { join } from 'node:path'
2-
import execa from 'execa'
3-
import { bold, green } from 'picocolors'
2+
import type { Options } from 'jscodeshift'
3+
import { run as jscodeshift } from 'jscodeshift/src/Runner'
4+
import { bold } from 'picocolors'
45
import prompts from 'prompts'
56
import { TRANSFORM_OPTIONS } from '../config'
6-
import { getAllFiles } from '../utils/file'
77

88
export function onCancel() {
9+
console.info('> Cancelled process. Program will stop now without any actions. \n')
910
process.exit(1)
1011
}
1112

12-
const jscodeshiftExecutable = require.resolve('.bin/jscodeshift')
1313
const transformerDirectory = join(__dirname, '../', 'transforms')
1414

15-
// biome-ignore lint/suspicious/noExplicitAny: 'Any' is used because options can be anything.
16-
export async function transform(codemodName: string, source: string, options: any): Promise<void> {
15+
export async function transform(codemodName?: string, source?: string, options?: Record<string, unknown>) {
1716
let codemodSelected = codemodName
1817
let sourceSelected = source
1918

20-
const { dry, print, verbose } = options
21-
22-
let existCodemod = TRANSFORM_OPTIONS.find(({ value }) => value === codemodSelected)
19+
const existCodemod = TRANSFORM_OPTIONS.find(({ value }) => value === codemodSelected)
2320

2421
if (!codemodSelected || (codemodSelected && !existCodemod)) {
2522
const res = await prompts(
@@ -39,7 +36,6 @@ export async function transform(codemodName: string, source: string, options: an
3936
)
4037

4138
codemodSelected = res.transformer
42-
existCodemod = TRANSFORM_OPTIONS.find(({ value }) => value === codemodSelected)
4339
}
4440

4541
if (!sourceSelected) {
@@ -56,37 +52,20 @@ export async function transform(codemodName: string, source: string, options: an
5652
sourceSelected = res.path
5753
}
5854

59-
const transformerPath = join(transformerDirectory, `${codemodSelected}.js`)
60-
61-
const args: string[] = []
62-
63-
if (dry) {
64-
args.push('--dry')
55+
if (!codemodSelected) {
56+
console.info('> Codemod is not selected. Exist the program. \n')
57+
process.exit(1)
6558
}
6659

67-
if (print) {
68-
args.push('--print')
69-
}
60+
const transformerPath = join(transformerDirectory, `${codemodSelected}.js`)
7061

71-
if (verbose) {
72-
args.push('--verbose=2')
62+
const args: Options = {
63+
...options,
64+
verbose: options?.verbose ? 2 : 0,
65+
babel: false,
66+
ignorePattern: '**/node_modules/**',
67+
extensions: 'cts,mts,ts,js,mjs,cjs',
7368
}
7469

75-
args.push('--no-babel')
76-
args.push('--ignore-pattern=**/node_modules/**')
77-
args.push('--extensions=cts,mts,ts,js,mjs,cjs')
78-
79-
const files = await getAllFiles(sourceSelected)
80-
81-
args.push('--transform', transformerPath, ...files.map((file) => file.toString()))
82-
83-
console.log(`Executing command: ${green('jscodeshift')} ${args.join(' ')}`)
84-
85-
const jscodeshiftProcess = execa(jscodeshiftExecutable, args, {
86-
// include ANSI color codes
87-
env: process.stdout.isTTY ? { FORCE_COLOR: 'true' } : {},
88-
})
89-
90-
jscodeshiftProcess.stdout?.pipe(process.stdout)
91-
jscodeshiftProcess.stderr?.pipe(process.stderr)
70+
await jscodeshift(transformerPath, [sourceSelected || ''], args)
9271
}

config.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,24 @@ export const TRANSFORM_OPTIONS = [
1414
value: 'v4-deprecated-signatures',
1515
version: '5.0.0',
1616
},
17+
{
18+
description: 'Reverse param order for "redirect" method',
19+
value: 'redirect',
20+
version: '5.0.0',
21+
},
22+
{
23+
description: 'Change request.param() to dedicated methods',
24+
value: 'req-param',
25+
version: '5.0.0',
26+
},
27+
{
28+
description: 'Convert method name "sendfile" to "sendFile"',
29+
value: 'send-file',
30+
version: '5.0.0',
31+
},
32+
{
33+
description: 'Convert method name "del" to "delete"',
34+
value: 'full-name-delete',
35+
version: '5.0.0',
36+
},
1737
]

index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const program = new Command(packageJson.name)
1616
.option('-d, --dry', 'Dry run (no changes are made to files)')
1717
.option('-p, --print', 'Print transformed files to stdout')
1818
.option('--verbose', 'Show more information about the transform process')
19+
.option('--silent', "Don't print anything to stdout")
1920
.usage('[codemod] [source] [options]')
2021
.action(transform)
2122
// Why this option is necessary is explained here: https://github.com/tj/commander.js/pull/1427

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ module.exports = {
33
transform: {
44
'\\.ts$': ['ts-jest', { isolatedModules: true }],
55
},
6+
modulePathIgnorePatterns: ['<rootDir>/build/'],
67
}

package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
"private": true,
44
"version": "0.0.1",
55
"description": "Codemods for updating express servers.",
6-
"main": "index.js",
6+
"main": "build/index.js",
77
"contributors": ["Sebastian Beltran <[email protected]>", "Filip Kudla <[email protected]>"],
88
"license": "MIT",
9-
"bin": "./index.js",
10-
"files": ["transforms/*.js", "commands/*.js", "utils/*.js", "config.js", "index.js"],
9+
"bin": "build/index.js",
10+
"files": ["build/transforms/*.js", "build/commands/*.js", "build/utils/*.js", "build/config.js", "build/index.js"],
1111
"scripts": {
1212
"dev": "tsc -d -w -p tsconfig.json",
1313
"build": "tsc -d -p tsconfig.json",
@@ -18,7 +18,6 @@
1818
},
1919
"dependencies": {
2020
"commander": "^12.1.0",
21-
"execa": "^5.1.1",
2221
"fast-glob": "^3.3.2",
2322
"jscodeshift": "^17.1.1",
2423
"picocolors": "^1.1.1",

tsconfig.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
"downlevelIteration": true,
88
"preserveWatchOutput": true,
99
"resolveJsonModule": true,
10-
"strictNullChecks": true
10+
"strictNullChecks": true,
11+
"outDir": "build"
1112
},
1213
"include": ["**/*.ts"],
13-
"exclude": ["node_modules", "transforms/__testfixtures__/**"]
14+
"exclude": ["node_modules", "build", "transforms/__testfixtures__/**", "__test__", "**/*.spec.ts"]
1415
}

0 commit comments

Comments
 (0)