Skip to content

Commit b7ad997

Browse files
committed
feat(jest-to-node-test): add initial implementation
1 parent 9641f96 commit b7ad997

File tree

9 files changed

+2622
-3
lines changed

9 files changed

+2622
-3
lines changed

package-lock.json

Lines changed: 1351 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"$schema": "https://codemod-utils.s3.us-west-1.amazonaws.com/configuration_schema.json",
3+
"name": "jest-to-node-test-runner",
4+
"description": "Convert Jest tests to Node Test Runner tests",
5+
"version": "1.0.0",
6+
"engine": "ast-grep",
7+
"private": false,
8+
"arguments": [],
9+
"meta": {
10+
"git": "https://github.com/nodejs/userland-migrations/tree/main/recipe/jest-to-node-test-runner",
11+
"tags": ["jest", "import", "node", "typescript"]
12+
}
13+
}

recipes/jest-to-node-test/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Jest to Node Test
2+
3+
This package transforms Jest test files into `node:test` files.
4+
5+
This is a one-and-done process, and the updated source-code should be committed to your version control (eg git); thereafter, source-code import statements should be authored compliant with the ECMAScript (JavaScript) standard.
6+
7+
## TODO:
8+
9+
- Remove `jest`, `@types/jest`, `@jest/globals` and related modules from `package.json`
10+
- Check/install `expect`
11+
- Update `package.json` scripts that use `jest`
12+
- remove `jest` config files
13+
- remove `jest` config from `package.json` and `tsconfig.json`
14+
- Migrate snapshots ✅
15+
- Convert mocks ✅
16+
- Convert setup/teardown files
17+
- Migrate assertions
18+
- Migrate test/it config (e.g. timeout)
19+
- Migrate suite methods (e.g. `it.each`)
20+
- Check non-TS CJS/ESM support
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "jest-to-node-test",
3+
"version": "1.0.0-rc.1",
4+
"description": "Convert Jest tests to node:test tests",
5+
"type": "module",
6+
"main": "./src/workflow.ts",
7+
"engines": {
8+
"node": ">=22.6.0"
9+
},
10+
"scripts": {
11+
"start": "node --no-warnings --experimental-import-meta-resolve --experimental-strip-types ./src/workflow.ts",
12+
"test": "node --no-warnings --experimental-import-meta-resolve --experimental-test-module-mocks --experimental-test-snapshots --experimental-strip-types --import='../../test/snapshots.ts' --test --experimental-test-coverage --test-coverage-include='src/**/*' --test-coverage-exclude='**/*.test.ts' './**/*.test.ts'"
13+
},
14+
"repository": {
15+
"type": "git",
16+
"url": "git+https://github.com/nodejs/userland-migrations.git",
17+
"directory": "recipes/jest-to-node-test-runner"
18+
},
19+
"files": [
20+
"README.md",
21+
".codemodrc.json",
22+
"bundle.js"
23+
],
24+
"keywords": [
25+
"codemod",
26+
"esm",
27+
"typescript"
28+
],
29+
"author": "Carlos Espa",
30+
"license": "MIT",
31+
"bugs": {
32+
"url": "https://github.com/nodejs/userland-migrations/issues"
33+
},
34+
"homepage": "https://github.com/nodejs/userland-migrations/tree/main/jest-to-node-test-runner#readme",
35+
"devDependencies": {
36+
"@jest/globals": "^29.7.0",
37+
"@types/node": "^22.15.5",
38+
"expect": "^29.7.0"
39+
},
40+
"dependencies": {
41+
"@ast-grep/napi": "^0.38.1"
42+
}
43+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { describe, it, expect, jest } from "@jest/globals";
2+
3+
describe("test", () => {
4+
it("should be a test", () => {
5+
const a = jest.fn((i: number, j: number) => i + j);
6+
const b = jest.fn(async (i: number, j: number) => i - j);
7+
jest.mock("./workflow.ts");
8+
9+
const logSpy = jest.spyOn(console, "log");
10+
console.log("Hello, world!");
11+
12+
expect(logSpy.mock.calls[0][0]).toBe("Hello, world!");
13+
14+
a(1, 1);
15+
a(2, 2);
16+
17+
expect(a).toHaveBeenCalled();
18+
expect(a).toBeCalled();
19+
20+
expect(a).toHaveBeenCalledTimes(2);
21+
expect(a).toBeCalledTimes(2);
22+
23+
expect(a).toHaveBeenCalledWith(1, 1);
24+
expect(a).toBeCalledWith(1, 1);
25+
26+
expect(a).toHaveBeenLastCalledWith(2, 2);
27+
expect(a).lastCalledWith(2, 2);
28+
29+
expect(a).toHaveBeenNthCalledWith(1, 1, 1);
30+
expect(a).nthCalledWith(2, 2, 2);
31+
32+
expect(a).toHaveReturned();
33+
expect(a).toReturn();
34+
35+
expect(a).toHaveReturnedTimes(2);
36+
expect(a).toReturnTimes(2);
37+
38+
expect(a).toHaveReturnedWith(2);
39+
expect(a).toReturnWith(4);
40+
41+
expect(a).toHaveLastReturnedWith(4);
42+
expect(a).lastReturnedWith(4);
43+
44+
expect(a).toHaveNthReturnedWith(1, 2);
45+
expect(a).nthReturnedWith(2, 4);
46+
47+
a.mock.calls;
48+
49+
a.mock.results;
50+
51+
// a.mock.instances;
52+
53+
// a.mock.contexts;
54+
55+
a.mock.lastCall;
56+
57+
a.mockClear();
58+
59+
a.mockReset();
60+
61+
a.mockRestore();
62+
63+
a.mockImplementation((i: number, j: number) => i * j);
64+
65+
a.mockImplementationOnce((i: number, j: number) => i - j);
66+
67+
// a.mockName("myMock");
68+
69+
// a.mockReturnThis();
70+
71+
a.mockReturnValue(42);
72+
73+
a.mockReturnValueOnce(42);
74+
75+
b.mockRejectedValue(new Error("Test error"));
76+
77+
b.mockRejectedValueOnce(new Error("Test error once"));
78+
79+
b.mockResolvedValue(42);
80+
81+
b.mockResolvedValueOnce(42);
82+
83+
expect({ a: 1 }).toMatchSnapshot();
84+
85+
expect({ b: 2 }).toMatchInlineSnapshot(`
86+
{
87+
"b": 2,
88+
}
89+
`);
90+
91+
jest.useFakeTimers();
92+
93+
jest.useRealTimers();
94+
95+
jest.runAllTicks();
96+
97+
jest.runAllTimers();
98+
99+
jest.runAllTimersAsync();
100+
101+
jest.runAllImmediates();
102+
103+
jest.advanceTimersByTime(1000);
104+
105+
jest.advanceTimersByTimeAsync(1000);
106+
107+
jest.runOnlyPendingTimers();
108+
109+
jest.runOnlyPendingTimersAsync();
110+
111+
// jest.advanceTimersToNextTimer(5);
112+
113+
// jest.advanceTimersToNextTimerAsync(5);
114+
115+
jest.clearAllTimers();
116+
117+
// jest.getTimerCount();
118+
119+
jest.now();
120+
121+
jest.setSystemTime(1000);
122+
123+
// jest.getRealSystemTime();
124+
});
125+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
exports[`workflow > should migrate jest to node:test 1`] = `
2+
"import { describe, it, mock } from \\"node:test\\";\\nimport { expect } from \\"expect\\";\\n\\ndescribe(\\"test\\", () => {\\n\\tit(\\"should be a test\\", (t) => {\\n\\t\\tconst a = mock.fn((i: number, j: number) => i + j);\\n\\t\\tconst b = mock.fn(async (i: number, j: number) => i - j);\\n\\t\\tmock.module(\\"./workflow.ts\\");\\n\\n\\t\\tconst logSpy = mock.method(console,\\"log\\");\\n\\t\\tconsole.log(\\"Hello, world!\\");\\n\\n\\t\\texpect(logSpy.mock.calls.map((call) => call.arguments)[0][0]).toBe(\\"Hello, world!\\");\\n\\n\\t\\ta(1, 1);\\n\\t\\ta(2, 2);\\n\\n\\t\\texpect(a.mock.callCount()).toBeTruthy();\\n\\t\\texpect(a.mock.callCount()).toBeTruthy();\\n\\n\\t\\texpect(a.mock.callCount()).toBe(2);\\n\\t\\texpect(a.mock.callCount()).toBe(2);\\n\\n\\t\\texpect(a.mock.calls.map(call => call.arguments)).toContainEqual([1,1]);\\n\\t\\texpect(a.mock.calls.map(call => call.arguments)).toContainEqual([1,1]);\\n\\n\\t\\texpect(a.mock.calls.at(-1)?.arguments).toStrictEqual([2,2]);\\n\\t\\texpect(a.mock.calls.at(-1)?.arguments).toStrictEqual([2,2]);\\n\\n\\t\\texpect(a.mock.calls[0].arguments).toStrictEqual([1,1]);\\n\\t\\texpect(a.mock.calls[1].arguments).toStrictEqual([2,2]);\\n\\n\\t\\texpect(a.mock.calls.some(call => call.error === undefined)).toBeTruthy();\\n\\t\\texpect(a.mock.calls.some(call => call.error === undefined)).toBeTruthy();\\n\\n\\t\\texpect(a.mock.calls.filter(call => call.error === undefined)).toHaveLength(2);\\n\\t\\texpect(a.mock.calls.filter(call => call.error === undefined)).toHaveLength(2);\\n\\n\\t\\texpect(a.mock.calls.map(call => call.result)).toContainEqual(2);\\n\\t\\texpect(a.mock.calls.map(call => call.result)).toContainEqual(4);\\n\\n\\t\\texpect(a.mock.calls.at(-1)?.result).toBe(4);\\n\\t\\texpect(a.mock.calls.at(-1)?.result).toBe(4);\\n\\n\\t\\texpect(a.mock.calls[0].result).toBe(2);\\n\\t\\texpect(a.mock.calls[1].result).toBe(4);\\n\\n\\t\\ta.mock.calls.map((call) => call.arguments);\\n\\n\\t\\ta.mock.calls.map((call) => ({\\n\\ttype: call.error ? \\"throw\\" : \\"return\\",\\n\\tvalue: call.error ? call.error : call.result,\\n}));\\n\\n\\t\\t// a.mock.instances;\\n\\n\\t\\t// a.mock.contexts;\\n\\n\\t\\ta.mock.calls.at(-1)?.arguments;\\n\\n\\t\\ta.mock.resetCalls();\\n\\n\\t\\ta.mock.restore();\\n\\n\\t\\ta.mock.restore();\\n\\n\\t\\ta.mock.mockImplementation((i: number, j: number) => i * j);\\n\\n\\t\\ta.mock.mockImplementationOnce((i: number, j: number) => i - j);\\n\\n\\t\\t// a.mockName(\\"myMock\\");\\n\\n\\t\\t// a.mockReturnThis();\\n\\n\\t\\ta.mock.mockImplementation(() => 42);\\n\\n\\t\\ta.mock.mockImplementationOnce(() => 42);\\n\\n\\t\\tb.mock.mockImplementation(async () => {\\n\\tthrow new Error(\\"Test error\\");\\n});\\n\\n\\t\\tb.mock.mockImplementationOnce(async () => {\\n\\tthrow new Error(\\"Test error once\\");\\n});\\n\\n\\t\\tb.mock.mockImplementation(async () => 42);\\n\\n\\t\\tb.mock.mockImplementationOnce(async () => 42);\\n\\n\\t\\tt.assert.snapshot({ a: 1 });\\n\\n\\t\\tt.assert.snapshot({ b: 2 });\\n\\n\\t\\tmock.timers.enable();\\n\\n\\t\\tmock.timers.reset();\\n\\n\\t\\tmock.timers.runAll();\\n\\n\\t\\tmock.timers.runAll();\\n\\n\\t\\tmock.timers.runAll();\\n\\n\\t\\tmock.timers.runAll();\\n\\n\\t\\tmock.timers.tick(1000);\\n\\n\\t\\tmock.timers.tick(1000);\\n\\n\\t\\tmock.timers.runAll();\\n\\n\\t\\tmock.timers.runAll();\\n\\n\\t\\t// jest.advanceTimersToNextTimer(5);\\n\\n\\t\\t// jest.advanceTimersToNextTimerAsync(5);\\n\\n\\t\\tmock.timers.reset();\\n\\n\\t\\tjest.getTimerCount();\\n\\n\\t\\tDate.now();\\n\\n\\t\\tmock.timers.setTime(Number(1000));\\n\\n\\t\\t// jest.getRealSystemTime();\\n\\t});\\n});\\n"
3+
`;

0 commit comments

Comments
 (0)