Skip to content

Commit d9b3cfb

Browse files
authored
Merge pull request #14 from dpuscher/migrate-to-vitest
chore(test): migrate jest to vitest with happy-dom
2 parents 12311df + db4e1f0 commit d9b3cfb

13 files changed

Lines changed: 1607 additions & 2474 deletions

File tree

.github/workflows/pr-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
run: yarn lint:css
4848

4949
- name: Run tests
50-
run: yarn test --ci --watch=false
50+
run: yarn test
5151

5252
- name: Build
5353
run: yarn build

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ You can adjust the port by setting the `PORT` environment variable.
7777

7878
## Running the tests
7979

80-
Jest is used as a test runner in this project:
80+
Vitest is used as a test runner in this project:
8181

8282
```
8383
yarn test
@@ -87,7 +87,7 @@ You can also use watch-mode and display the current test coverage:
8787

8888
```
8989
yarn test:watch
90-
yarm test:coverage
90+
yarn test:coverage
9191
```
9292

9393
### Coding style tests

app/__mocks__/redis.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
11
/* eslint-disable no-underscore-dangle */
22

3-
const store = new Map();
3+
const store = new Map<string, string>();
44

5-
const mockGet = jest.fn(async key => store.get(key) || null);
6-
const mockSet = jest.fn(async (key, value) => {
5+
export const _get = vi.fn(async (key: string) => store.get(key) || null);
6+
export const _set = vi.fn(async (key: string, value: string) => {
77
store.set(key, value);
88
return "OK";
99
});
10-
const mockConnect = jest.fn(async () => {});
11-
const mockOn = jest.fn();
10+
const mockConnect = vi.fn(async () => {});
11+
const mockOn = vi.fn();
1212

13-
const createClient = jest.fn(() => ({
14-
get: mockGet,
15-
set: mockSet,
13+
export const createClient = vi.fn(() => ({
14+
get: _get,
15+
set: _set,
1616
connect: mockConnect,
1717
on: mockOn,
1818
}));
1919

20-
module.exports = {
21-
createClient,
22-
_get: mockGet,
23-
_set: mockSet,
24-
_reset: () => store.clear(),
25-
};
20+
export const _reset = () => store.clear();

app/spec/cache.spec.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
/* eslint-disable no-underscore-dangle */
22

3+
import * as redis from "redis";
34
import * as Cache from "../cache";
45

5-
// eslint-disable-next-line @typescript-eslint/no-require-imports
6-
const redis = require("redis");
7-
8-
jest.mock("redis");
6+
vi.mock("redis", async () => import("../__mocks__/redis"));
97

108
describe("cache", () => {
119
beforeEach(() => {
12-
redis._get.mockClear();
13-
redis._set.mockClear();
14-
redis._reset();
10+
(redis as any)._get.mockClear();
11+
(redis as any)._set.mockClear();
12+
(redis as any)._reset();
1513
});
1614

1715
describe("set", () => {
@@ -21,10 +19,10 @@ describe("cache", () => {
2119

2220
await Cache.set(key, value);
2321

24-
expect(redis._set.mock.calls.length).toBe(1);
25-
expect(redis._set.mock.calls[0][0]).toBe(key);
26-
expect(redis._set.mock.calls[0][1]).toBe(JSON.stringify(value));
27-
expect(redis._set.mock.calls[0][2]).toEqual({ EX: 86400 });
22+
expect((redis as any)._set.mock.calls.length).toBe(1);
23+
expect((redis as any)._set.mock.calls[0][0]).toBe(key);
24+
expect((redis as any)._set.mock.calls[0][1]).toBe(JSON.stringify(value));
25+
expect((redis as any)._set.mock.calls[0][2]).toEqual({ EX: 86400 });
2826
});
2927

3028
it("passes given ttl to redis store", async () => {
@@ -34,7 +32,7 @@ describe("cache", () => {
3432

3533
await Cache.set(key, value, ttl);
3634

37-
expect(redis._set.mock.calls[0][2]).toEqual({ EX: ttl });
35+
expect((redis as any)._set.mock.calls[0][2]).toEqual({ EX: ttl });
3836
});
3937

4038
it("uses 24 hours as default ttl", async () => {
@@ -43,7 +41,7 @@ describe("cache", () => {
4341

4442
await Cache.set(key, value);
4543

46-
expect(redis._set.mock.calls[0][2]).toEqual({ EX: 24 * 60 * 60 });
44+
expect((redis as any)._set.mock.calls[0][2]).toEqual({ EX: 24 * 60 * 60 });
4745
});
4846
});
4947

@@ -57,8 +55,8 @@ describe("cache", () => {
5755
// ignore errors
5856
}
5957

60-
expect(redis._get.mock.calls.length).toBe(1);
61-
expect(redis._get.mock.calls[0][0]).toBe(key);
58+
expect((redis as any)._get.mock.calls.length).toBe(1);
59+
expect((redis as any)._get.mock.calls[0][0]).toBe(key);
6260
});
6361

6462
it("returns correct data from redis store after it was saved", async () => {
@@ -71,10 +69,10 @@ describe("cache", () => {
7169
expect(cachedData).toEqual(value);
7270
});
7371

74-
it("rejects the promise when no data is stored in redis", () => {
72+
it("rejects the promise when no data is stored in redis", async () => {
7573
const key = "foo";
7674

77-
expect(Cache.get(key)).rejects.toBeUndefined();
75+
await expect(Cache.get(key)).rejects.toBeUndefined();
7876
});
7977
});
8078
});

components/profile/actions/spec/autoScrobbleActions.spec.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,19 @@ const demoAutoScrobbleData = [
1818
},
1919
];
2020

21+
const mockJsonResponse = data =>
22+
({
23+
ok: true,
24+
status: 200,
25+
json: async () => data,
26+
}) as Response;
27+
2128
describe("historyActions", () => {
29+
let fetchMock;
2230
beforeEach(() => {
23-
fetch.mockResponse(JSON.stringify(demoAutoScrobbleData));
24-
});
25-
afterEach(() => {
26-
fetch.resetMocks();
31+
fetchMock = vi
32+
.spyOn(globalThis, "fetch")
33+
.mockResolvedValue(mockJsonResponse(demoAutoScrobbleData));
2734
});
2835

2936
describe("fetchAutoScrobbles", () => {
@@ -52,7 +59,7 @@ describe("historyActions", () => {
5259

5360
it("sets error state to store when loading fails", () => {
5461
const error = new Error("Foooo!");
55-
fetch.mockReject(error);
62+
fetchMock.mockRejectedValue(error);
5663
const expectedAction = actionCreators.setErrorState(error);
5764
const store = emptyStore();
5865

@@ -72,8 +79,8 @@ describe("historyActions", () => {
7279
const store = emptyStore();
7380

7481
return store.dispatch(fetchAutoScrobbles()).then(() => {
75-
expect(fetch.mock.calls.length).toEqual(1);
76-
expect(fetch.mock.calls[0][0]).toEqual("/api/user/autoscrobbles");
82+
expect(fetchMock.mock.calls.length).toEqual(1);
83+
expect(fetchMock.mock.calls[0][0]).toEqual("/api/user/autoscrobbles");
7784
});
7885
});
7986
});
@@ -110,7 +117,7 @@ describe("historyActions", () => {
110117
it("sets error state to store when loading fails", () => {
111118
const id = 1337;
112119
const error = new Error("Foooo!");
113-
fetch.mockReject(error);
120+
fetchMock.mockRejectedValue(error);
114121
const expectedAction = actionCreators.setErrorState(error);
115122
const store = emptyStore();
116123

@@ -134,10 +141,10 @@ describe("historyActions", () => {
134141
const store = emptyStore();
135142

136143
return store.dispatch(deleteAutoScrobble(id)).then(() => {
137-
expect(fetch.mock.calls.length).toEqual(1);
138-
expect(fetch.mock.calls[0][0]).toEqual("/api/user/autoscrobbles");
139-
expect(fetch.mock.calls[0][1].method).toEqual("DELETE");
140-
expect(fetch.mock.calls[0][1].body).toEqual(JSON.stringify({ id }));
144+
expect(fetchMock.mock.calls.length).toEqual(1);
145+
expect(fetchMock.mock.calls[0][0]).toEqual("/api/user/autoscrobbles");
146+
expect(fetchMock.mock.calls[0][1].method).toEqual("DELETE");
147+
expect(fetchMock.mock.calls[0][1].body).toEqual(JSON.stringify({ id }));
141148
});
142149
});
143150
});

components/profile/actions/spec/historyActions.spec.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,19 @@ const demoHistoryData = [
2121
},
2222
];
2323

24+
const mockJsonResponse = data =>
25+
({
26+
ok: true,
27+
status: 200,
28+
json: async () => data,
29+
}) as Response;
30+
2431
describe("historyActions", () => {
32+
let fetchMock;
2533
beforeEach(() => {
26-
fetch.mockResponse(JSON.stringify(demoHistoryData));
27-
});
28-
afterEach(() => {
29-
fetch.resetMocks();
34+
fetchMock = vi
35+
.spyOn(globalThis, "fetch")
36+
.mockResolvedValue(mockJsonResponse(demoHistoryData));
3037
});
3138

3239
it("sets loading state to true as first action", () => {
@@ -52,7 +59,7 @@ describe("historyActions", () => {
5259

5360
it("sets error state to store when loading fails", () => {
5461
const error = new Error("Foooo!");
55-
fetch.mockReject(error);
62+
fetchMock.mockRejectedValue(error);
5663
const expectedAction = actionCreators.setErrorState(error);
5764
const store = emptyStore();
5865

@@ -70,8 +77,8 @@ describe("historyActions", () => {
7077
const store = emptyStore();
7178

7279
return store.dispatch(fetchHistory()).then(() => {
73-
expect(fetch.mock.calls.length).toEqual(1);
74-
expect(fetch.mock.calls[0][0]).toEqual("/api/user/history");
80+
expect(fetchMock.mock.calls.length).toEqual(1);
81+
expect(fetchMock.mock.calls[0][0]).toEqual("/api/user/history");
7582
});
7683
});
7784
});

config/setupTests.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
import fetchMock from "jest-fetch-mock";
2-
3-
global.fetch = fetchMock as any;
1+
afterEach(() => {
2+
vi.restoreAllMocks();
3+
vi.unstubAllGlobals();
4+
});

eslint.config.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
const { FlatCompat } = require("@eslint/eslintrc");
22
const js = require("@eslint/js");
33
const prettierConfig = require("eslint-config-prettier/flat");
4+
const vitestModule = require("@vitest/eslint-plugin");
5+
6+
const vitest = vitestModule.default ?? vitestModule;
47

58
const compat = new FlatCompat({
69
baseDirectory: __dirname,
@@ -29,20 +32,19 @@ module.exports = [
2932
"plugin:import/recommended",
3033
),
3134
{
32-
files: ["**/*.spec.{js,jsx,ts,tsx}", "**/*.test.{js,jsx,ts,tsx}"],
35+
files: ["**/*.{spec,test}.{js,jsx,ts,tsx}"],
36+
plugins: {
37+
vitest,
38+
},
3339
languageOptions: {
3440
globals: {
35-
describe: "readonly",
36-
it: "readonly",
37-
test: "readonly",
38-
expect: "readonly",
39-
beforeAll: "readonly",
40-
beforeEach: "readonly",
41-
afterAll: "readonly",
42-
afterEach: "readonly",
43-
jest: "readonly",
41+
...vitest.environments.env.globals,
4442
},
4543
},
44+
rules: {
45+
...vitest.configs.recommended.rules,
46+
"vitest/no-importing-vitest-globals": "error",
47+
},
4648
},
4749
{
4850
rules: {

jest.config.js

Lines changed: 0 additions & 16 deletions
This file was deleted.

package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
"dev": "next dev",
1717
"build": "next build",
1818
"start": "next start",
19-
"test": "jest",
20-
"test:watch": "yarn test --watch",
21-
"test:coverage": "yarn test --coverage",
19+
"test": "vitest run",
20+
"test:watch": "vitest",
21+
"test:coverage": "vitest run --coverage",
2222
"lint": "eslint .",
2323
"lint:fix": "eslint . --fix",
2424
"lint:css": "stylelint './**/*.styles.ts'",
@@ -60,21 +60,20 @@
6060
"@lavamoat/preinstall-always-fail": "^2.1.1",
6161
"@testing-library/dom": "^10.0.0",
6262
"@testing-library/react": "^16.0.0",
63-
"@types/jest": "^30.0.0",
6463
"@types/node": "^22.10.7",
6564
"@types/react": "^18.3.18",
6665
"@types/react-dom": "^18.3.5",
67-
"babel-jest": "^29.7.0",
66+
"@vitejs/plugin-react": "^4.4.1",
67+
"@vitest/coverage-v8": "^2.1.8",
68+
"@vitest/eslint-plugin": "^1.3.25",
6869
"babel-plugin-styled-components": "^2.1.4",
6970
"eslint": "^8.57.0",
7071
"eslint-config-next": "^14.2.0",
7172
"eslint-config-prettier": "10.1.8",
7273
"eslint-plugin-import": "^2.31.0",
7374
"eslint-plugin-jsx-a11y": "^6.10.2",
75+
"happy-dom": "^15.11.7",
7476
"husky": "^9.0.0",
75-
"jest": "^29.7.0",
76-
"jest-environment-jsdom": "^29.7.0",
77-
"jest-fetch-mock": "^3.0.1",
7877
"lint-staged": "^16.2.4",
7978
"postcss-styled-syntax": "^0.7.1",
8079
"prettier": "^3.8.1",
@@ -83,14 +82,15 @@
8382
"redux-mock-store": "^1.5.4",
8483
"stylelint": "^16.12.0",
8584
"stylelint-config-recommended": "^14.0.1",
86-
"typescript": "^5.7.3"
85+
"typescript": "^5.7.3",
86+
"vite": "^6.1.0",
87+
"vite-tsconfig-paths": "^5.1.4",
88+
"vitest": "^2.1.8"
8789
},
8890
"packageManager": "yarn@4.12.0",
8991
"lavamoat": {
9092
"allowScripts": {
9193
"@lavamoat/preinstall-always-fail": false,
92-
"babel-jest>@jest/transform>jest-haste-map>fsevents": false,
93-
"jest-environment-jsdom>jest-util>jest-util>fsevents": false,
9494
"@babel/runtime-corejs2>core-js": false,
9595
"eslint-config-next>eslint-import-resolver-typescript>unrs-resolver": false
9696
}

0 commit comments

Comments
 (0)