Skip to content

Commit 77b916a

Browse files
committed
feature #1202 Migrate from Jest to Vitest (ChqThomas, weaverryan)
This PR was merged into the 2.x branch. Discussion ---------- Migrate from Jest to Vitest | Q | A | ------------- | --- | Bug fix? | no | New feature? | no <!-- please update src/**/CHANGELOG.md files --> | Tickets | #1018 #610 | License | MIT This PR is a draft to test the feasibility of migrating from [jest ](https://jestjs.io/) to [vitest](https://vitest.dev/) Jest doesn't support ESM out of the box, which makes it difficult to test some libraries like Chart.js and Svelte v4. Vitest supports typescript and ESM by default and is compatible with Jest API, it'll help us test those packages and maybe others in the future #### Infos: There is a global config file `vitest.config.js` which contains the default settings for every package, when needed it can be extended with a file in the package root (ex: `src\React\assets\vitest.config.js`) I replaced the `yarn test` script with `vitest src --run` so it's run globally and the CI doesn't stop at the first error from a package, it still can be run for every/individual packages with `yarn workspaces run vitest --run` / `yarn workspace "`@symfony`/ux-live-component" run vitest --run` Libraries like fetch-mock-jest and jest-canvas-mock need to be replaced The `require_context_poylfill.ts` file used by ux-react, ux-vue and ux-svelte needs to be modified as it uses a dynamic `require`, replacing it with an `import` doesn't work as it is asynchronous #### Resources: - https://vitest.dev/guide/migration.html - https://srivastavaankita080.medium.com/jest-vitest-migration-79f4735dd5d0 ### TODO: - Packages: - [x] `@symfony`/ux-autocomplete `0/14` - [x] replace fetch-mock-jest ? - [x] `@symfony`/ux-chartjs `0/4` - [x] fix failing tests - [x] replace jest-canvas-mock ? - [x] `@symfony`/ux-cropperjs `1/1` - [x] `@symfony`/ux-dropzone `3/3` - [x] `@symfony`/ux-lazy-image `1/1` - [x] `@symfony`/ux-live-component `217/217` 💯 - [x] `Error: Uncaught [ReferenceError: Node is not defined]` Just a warning but the test suite pass - [x] `@symfony`/ux-notify `1/1` - [x] `@symfony`/ux-react `0/4` - [x] install `@vitejs`/plugin-react - [x] migrate `require_context_poylfill.ts` - [x] `@symfony`/stimulus-bundle `1/1` - [x] `Error: Uncaught [ReferenceError: Node is not defined]` Just a warning but the test suite pass - [x] `@symfony`/ux-svelte `0/4` - [x] replace svelte-jester with `@sveltejs`/vite-plugin-svelte - [x] migrate `require_context_poylfill.ts` - [x] fix failing tests - [x] `@symfony`/ux-swup `0/5` - [x] `require() of ES Module node_modules/delegate-it/index.js from node_modules/swup/lib/index.js not supported.` - [x] `@symfony`/ux-toggle-password `1/1` - [x] `@symfony`/ux-translator `91/91` - [x] `@symfony`/ux-turbo `0/2` - [x] `TypeError: Cannot read properties of undefined (reading 'prototype')` - [x] `@symfony`/ux-typed `1/1` - [x] `@symfony`/ux-vue `0/5` - [x] replace `@vue`/vue3-jest with `@vitejs`/plugin-vue - [x] migrate `require_context_poylfill.ts` - [x] Remove jest - [x] Cleanup - [ ] Benchmark ? I'll need help to resolve these issues 😁 Commits ------- 76d82de Removing need for jq 7f37e59 vitest.config.js format f953d52 Remove jest config and dependencies de32106 Import vitest when needed instead of adding global types in tsconfig 9322bc7 format 2be4088 re-adding module a33fc1d Fixing final tests f59f4f6 Working around Turbo issue 13c7431 dropping support for very old versions of swup ecba87b Adding script to run all tests, but keep going if a previous one fails 238d25e Stopping Stimulus to avoid side effects after the test cf29587 Replacing require.context polyfill with a simpler implementation ef34031 Swapping in vitest-canvas-mock 2270406 Switching to vitest-fetch-mock 9043da3 Removing no-longer-needed setup file c1db340 Fixing problem where Stimulus sometimes continued after the test 10c5591 Add forgotten package.json from ux-react package e9e16eb Wip trying to migrate from jest to vitest
2 parents 97fe2cd + 76d82de commit 77b916a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+3411
-1286
lines changed

bin/run-vitest-all.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/bash
2+
3+
# Get all workspace names
4+
workspaces=$(yarn workspaces info | grep -o '@symfony/[^"]*')
5+
6+
# Flag to track if any test fails
7+
all_tests_passed=true
8+
9+
for workspace in $workspaces; do
10+
echo "Running tests in $workspace..."
11+
12+
# Run the tests and if they fail, set the flag to false
13+
yarn workspace $workspace run vitest --run || { echo "$workspace failed"; all_tests_passed=false; }
14+
done
15+
16+
# Check the flag at the end and exit with code 1 if any test failed
17+
if [ "$all_tests_passed" = false ]; then
18+
echo "Some tests failed."
19+
exit 1
20+
else
21+
echo "All tests passed!"
22+
exit 0
23+
fi

jest.config.js

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

package.json

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
],
66
"scripts": {
77
"build": "node bin/build_javascript.js && node bin/build_styles.js",
8-
"test": "yarn workspaces run jest",
8+
"test": "bin/run-vitest-all.sh",
99
"lint": "yarn workspaces run eslint src test",
1010
"format": "prettier src/*/assets/src/*.ts src/*/assets/test/*.js {,src/*/}*.{json,md} --write",
1111
"check-lint": "yarn lint --no-fix",
@@ -22,16 +22,14 @@
2222
"@symfony/stimulus-testing": "^2.0.1",
2323
"@typescript-eslint/eslint-plugin": "^5.2.0",
2424
"@typescript-eslint/parser": "^5.2.0",
25-
"babel-jest": "^27.3.1",
2625
"clean-css-cli": "^5.6.2",
2726
"eslint": "^8.1.0",
2827
"eslint-config-prettier": "^8.0.0",
29-
"eslint-plugin-jest": "^25.2.2",
30-
"jest": "^27.3.1",
3128
"prettier": "^2.2.1",
3229
"rollup": "^3.7.0",
3330
"tslib": "^2.3.1",
34-
"typescript": "^4.4.4"
31+
"typescript": "^4.4.4",
32+
"vitest": "^0.34.6"
3533
},
3634
"eslintConfig": {
3735
"root": true,
@@ -61,9 +59,6 @@
6159
{
6260
"files": [
6361
"src/*/assets/test/**/*.ts"
64-
],
65-
"extends": [
66-
"plugin:jest/recommended"
6762
]
6863
}
6964
]

src/Autocomplete/.gitattributes

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,5 @@
44
/phpunit.xml.dist export-ignore
55
/assets/.gitignore export-ignore
66
/assets/src/**/*.ts export-ignore
7-
/assets/jest.config.js export-ignore
87
/assets/test export-ignore
98
/tests export-ignore

src/Autocomplete/assets/jest.config.js

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

src/Autocomplete/assets/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
},
3030
"devDependencies": {
3131
"@hotwired/stimulus": "^3.0.0",
32-
"fetch-mock-jest": "^1.5.1",
33-
"tom-select": "^2.2.2"
32+
"tom-select": "^2.2.2",
33+
"vitest-fetch-mock": "^0.2.2"
3434
}
3535
}

src/Autocomplete/assets/test/controller.test.ts

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ import AutocompleteController, {
1515
AutocompleteConnectOptions,
1616
AutocompletePreConnectOptions,
1717
} from '../src/controller';
18-
import fetchMock from 'fetch-mock-jest';
1918
import userEvent from '@testing-library/user-event';
2019
import TomSelect from 'tom-select';
20+
import createFetchMock from 'vitest-fetch-mock';
21+
import { vi } from 'vitest';
2122

2223
const shortDelay = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
2324

@@ -50,19 +51,21 @@ const startAutocompleteTest = async (html: string): Promise<{ container: HTMLEle
5051
return { container, tomSelect };
5152
}
5253

54+
const fetchMocker = createFetchMock(vi);
5355
describe('AutocompleteController', () => {
5456
beforeAll(() => {
5557
const application = Application.start();
5658
application.register('autocomplete', AutocompleteController);
59+
60+
fetchMocker.enableMocks();
61+
});
62+
63+
beforeEach(() => {
64+
fetchMocker.resetMocks();
5765
});
5866

5967
afterEach(() => {
6068
document.body.innerHTML = '';
61-
62-
if (!fetchMock.done()) {
63-
throw new Error('Mocked requests did not match');
64-
}
65-
fetchMock.reset();
6669
});
6770

6871
it('connect without options', async () => {
@@ -74,6 +77,7 @@ describe('AutocompleteController', () => {
7477
`);
7578

7679
expect(tomSelect.input).toBe(getByTestId(container, 'main-element'));
80+
expect(fetchMock.requests().length).toEqual(0);
7781
});
7882

7983
it('connect with ajax URL on a select element', async () => {
@@ -88,8 +92,7 @@ describe('AutocompleteController', () => {
8892
`);
8993

9094
// initial Ajax request on focus
91-
fetchMock.mock(
92-
'/path/to/autocomplete?query=',
95+
fetchMock.mockResponseOnce(
9396
JSON.stringify({
9497
results: [
9598
{
@@ -100,8 +103,7 @@ describe('AutocompleteController', () => {
100103
}),
101104
);
102105

103-
fetchMock.mock(
104-
'/path/to/autocomplete?query=foo',
106+
fetchMock.mockResponseOnce(
105107
JSON.stringify({
106108
results: [
107109
{
@@ -132,6 +134,10 @@ describe('AutocompleteController', () => {
132134
await waitFor(() => {
133135
expect(container.querySelectorAll('.option[data-selectable]')).toHaveLength(2);
134136
});
137+
138+
expect(fetchMock.requests().length).toEqual(2);
139+
expect(fetchMock.requests()[0].url).toEqual('/path/to/autocomplete?query=');
140+
expect(fetchMock.requests()[1].url).toEqual('/path/to/autocomplete?query=foo');
135141
});
136142

137143
it('connect with ajax URL on an input element', async () => {
@@ -146,8 +152,7 @@ describe('AutocompleteController', () => {
146152
`);
147153

148154
// initial Ajax request on focus
149-
fetchMock.mock(
150-
'/path/to/autocomplete?query=',
155+
fetchMock.mockResponseOnce(
151156
JSON.stringify({
152157
results: [
153158
{
@@ -158,8 +163,7 @@ describe('AutocompleteController', () => {
158163
}),
159164
);
160165

161-
fetchMock.mock(
162-
'/path/to/autocomplete?query=foo',
166+
fetchMock.mockResponseOnce(
163167
JSON.stringify({
164168
results: [
165169
{
@@ -190,6 +194,10 @@ describe('AutocompleteController', () => {
190194
await waitFor(() => {
191195
expect(container.querySelectorAll('.option[data-selectable]')).toHaveLength(2);
192196
});
197+
198+
expect(fetchMock.requests().length).toEqual(2);
199+
expect(fetchMock.requests()[0].url).toEqual('/path/to/autocomplete?query=');
200+
expect(fetchMock.requests()[1].url).toEqual('/path/to/autocomplete?query=foo');
193201
});
194202

195203
it('limits updates when min-characters', async () => {
@@ -212,6 +220,8 @@ describe('AutocompleteController', () => {
212220
await waitFor(() => {
213221
expect(container.querySelectorAll('.option[data-selectable]')).toHaveLength(0);
214222
});
223+
224+
expect(fetchMock.requests().length).toEqual(0);
215225
});
216226

217227
it('min-characters can be a falsy value', async () => {
@@ -241,8 +251,7 @@ describe('AutocompleteController', () => {
241251
const controlInput = tomSelect.control_input;
242252

243253
// ajax call from initial focus
244-
fetchMock.mock(
245-
'/path/to/autocomplete?query=',
254+
fetchMock.mockResponseOnce(
246255
JSON.stringify({
247256
results: [
248257
{
@@ -267,8 +276,7 @@ describe('AutocompleteController', () => {
267276
});
268277

269278
// now trigger a load
270-
fetchMock.mock(
271-
'/path/to/autocomplete?query=foo',
279+
fetchMock.mockResponseOnce(
272280
JSON.stringify({
273281
results: [
274282
{
@@ -290,8 +298,7 @@ describe('AutocompleteController', () => {
290298
});
291299

292300
// now go below the min characters, but it should still load
293-
fetchMock.mock(
294-
'/path/to/autocomplete?query=fo',
301+
fetchMock.mockResponseOnce(
295302
JSON.stringify({
296303
results: [
297304
{
@@ -315,6 +322,11 @@ describe('AutocompleteController', () => {
315322
await waitFor(() => {
316323
expect(container.querySelectorAll('.option[data-selectable]')).toHaveLength(3);
317324
});
325+
326+
expect(fetchMock.requests().length).toEqual(3);
327+
expect(fetchMock.requests()[0].url).toEqual('/path/to/autocomplete?query=');
328+
expect(fetchMock.requests()[1].url).toEqual('/path/to/autocomplete?query=foo');
329+
expect(fetchMock.requests()[2].url).toEqual('/path/to/autocomplete?query=fo');
318330
});
319331

320332
it('adds work-around for live-component & multiple select', async () => {
@@ -359,8 +371,7 @@ describe('AutocompleteController', () => {
359371
`);
360372

361373
// initial Ajax request on focus
362-
fetchMock.mock(
363-
'/path/to/autocomplete?query=',
374+
fetchMock.mockResponseOnce(
364375
JSON.stringify({
365376
results: [
366377
{value: 1, text: 'dog1'},
@@ -390,8 +401,7 @@ describe('AutocompleteController', () => {
390401
throw new Error('cannot find dropdown content element');
391402
}
392403

393-
fetchMock.mock(
394-
'/path/to/autocomplete?query=&page=2',
404+
fetchMock.mockResponseOnce(
395405
JSON.stringify({
396406
results: [
397407
{value: 11, text: 'dog11'},
@@ -407,6 +417,10 @@ describe('AutocompleteController', () => {
407417
await waitFor(() => {
408418
expect(container.querySelectorAll('.option[data-selectable]')).toHaveLength(12);
409419
});
420+
421+
expect(fetchMock.requests().length).toEqual(2);
422+
expect(fetchMock.requests()[0].url).toEqual('/path/to/autocomplete?query=');
423+
expect(fetchMock.requests()[1].url).toEqual('/path/to/autocomplete?query=&page=2');
410424
});
411425

412426
it('continues working even if options html rearranges', async () => {
@@ -625,8 +639,7 @@ describe('AutocompleteController', () => {
625639
`);
626640

627641
// initial Ajax request on focus with group_by options
628-
fetchMock.mock(
629-
'/path/to/autocomplete?query=',
642+
fetchMock.mockResponseOnce(
630643
JSON.stringify({
631644
results: {
632645
options: [
@@ -665,8 +678,7 @@ describe('AutocompleteController', () => {
665678
}),
666679
);
667680

668-
fetchMock.mock(
669-
'/path/to/autocomplete?query=foo',
681+
fetchMock.mockResponseOnce(
670682
JSON.stringify({
671683
results: {
672684
options: [
@@ -709,5 +721,9 @@ describe('AutocompleteController', () => {
709721
expect(container.querySelectorAll('.option[data-selectable]')).toHaveLength(2);
710722
expect(container.querySelectorAll('.optgroup-header')).toHaveLength(1);
711723
});
724+
725+
expect(fetchMock.requests().length).toEqual(2);
726+
expect(fetchMock.requests()[0].url).toEqual('/path/to/autocomplete?query=');
727+
expect(fetchMock.requests()[1].url).toEqual('/path/to/autocomplete?query=foo');
712728
});
713729
});

src/Autocomplete/assets/test/setup.js

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

src/Chartjs/.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
/phpunit.xml.dist export-ignore
55
/assets/src/**/*.ts export-ignore
66
/assets/test export-ignore
7-
/assets/jest.config.js export-ignore
7+
/assets/vitest.config.js export-ignore
88
/tests export-ignore

src/Chartjs/assets/jest.config.js

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

src/Chartjs/assets/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"@hotwired/stimulus": "^3.0.0",
2828
"@types/chart.js": "^2.9.34",
2929
"chart.js": "^3.4.1 <3.9",
30-
"jest-canvas-mock": "^2.3.0",
31-
"resize-observer-polyfill": "^1.5.1"
30+
"resize-observer-polyfill": "^1.5.1",
31+
"vitest-canvas-mock": "^0.3.3"
3232
}
3333
}

src/Chartjs/assets/test/setup.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99

1010
'use strict';
1111

12-
import 'jest-canvas-mock';
12+
import 'vitest-canvas-mock';
1313
// eslint-disable-next-line
1414
global.ResizeObserver = require('resize-observer-polyfill');

src/Chartjs/assets/vitest.config.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { defineConfig, mergeConfig } from 'vitest/config';
2+
import configShared from '../../../vitest.config.js'
3+
import path from 'path';
4+
5+
export default mergeConfig(
6+
configShared,
7+
defineConfig({
8+
test: {
9+
setupFiles: [path.join(__dirname, 'test', 'setup.js')],
10+
deps: {
11+
optimizer: {
12+
web: {
13+
include: ['vitest-canvas-mock'],
14+
},
15+
},
16+
},
17+
}
18+
})
19+
);

src/Cropperjs/.gitattributes

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@
44
/phpunit.xml.dist export-ignore
55
/assets/src/**/*.ts export-ignore
66
/assets/test export-ignore
7-
/assets/jest.config.js export-ignore
87
/tests export-ignore

src/Cropperjs/assets/jest.config.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/Dropzone/.gitattributes

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@
44
/phpunit.xml.dist export-ignore
55
/assets/src/**/*.ts export-ignore
66
/assets/test export-ignore
7-
/assets/jest.config.js export-ignore
87
/tests export-ignore

src/Dropzone/assets/jest.config.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)