Skip to content

Commit

Permalink
feature symfony#1202 Migrate from Jest to Vitest (ChqThomas, weaverryan)
Browse files Browse the repository at this point in the history
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       | symfony#1018 symfony#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
  • Loading branch information
weaverryan committed Oct 22, 2023
2 parents 97fe2cd + 76d82de commit 77b916a
Show file tree
Hide file tree
Showing 65 changed files with 3,411 additions and 1,286 deletions.
23 changes: 23 additions & 0 deletions bin/run-vitest-all.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/bash

# Get all workspace names
workspaces=$(yarn workspaces info | grep -o '@symfony/[^"]*')

# Flag to track if any test fails
all_tests_passed=true

for workspace in $workspaces; do
echo "Running tests in $workspace..."

# Run the tests and if they fail, set the flag to false
yarn workspace $workspace run vitest --run || { echo "$workspace failed"; all_tests_passed=false; }
done

# Check the flag at the end and exit with code 1 if any test failed
if [ "$all_tests_passed" = false ]; then
echo "Some tests failed."
exit 1
else
echo "All tests passed!"
exit 0
fi
12 changes: 0 additions & 12 deletions jest.config.js

This file was deleted.

11 changes: 3 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
],
"scripts": {
"build": "node bin/build_javascript.js && node bin/build_styles.js",
"test": "yarn workspaces run jest",
"test": "bin/run-vitest-all.sh",
"lint": "yarn workspaces run eslint src test",
"format": "prettier src/*/assets/src/*.ts src/*/assets/test/*.js {,src/*/}*.{json,md} --write",
"check-lint": "yarn lint --no-fix",
Expand All @@ -22,16 +22,14 @@
"@symfony/stimulus-testing": "^2.0.1",
"@typescript-eslint/eslint-plugin": "^5.2.0",
"@typescript-eslint/parser": "^5.2.0",
"babel-jest": "^27.3.1",
"clean-css-cli": "^5.6.2",
"eslint": "^8.1.0",
"eslint-config-prettier": "^8.0.0",
"eslint-plugin-jest": "^25.2.2",
"jest": "^27.3.1",
"prettier": "^2.2.1",
"rollup": "^3.7.0",
"tslib": "^2.3.1",
"typescript": "^4.4.4"
"typescript": "^4.4.4",
"vitest": "^0.34.6"
},
"eslintConfig": {
"root": true,
Expand Down Expand Up @@ -61,9 +59,6 @@
{
"files": [
"src/*/assets/test/**/*.ts"
],
"extends": [
"plugin:jest/recommended"
]
}
]
Expand Down
1 change: 0 additions & 1 deletion src/Autocomplete/.gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@
/phpunit.xml.dist export-ignore
/assets/.gitignore export-ignore
/assets/src/**/*.ts export-ignore
/assets/jest.config.js export-ignore
/assets/test export-ignore
/tests export-ignore
5 changes: 0 additions & 5 deletions src/Autocomplete/assets/jest.config.js

This file was deleted.

4 changes: 2 additions & 2 deletions src/Autocomplete/assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
},
"devDependencies": {
"@hotwired/stimulus": "^3.0.0",
"fetch-mock-jest": "^1.5.1",
"tom-select": "^2.2.2"
"tom-select": "^2.2.2",
"vitest-fetch-mock": "^0.2.2"
}
}
72 changes: 44 additions & 28 deletions src/Autocomplete/assets/test/controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import AutocompleteController, {
AutocompleteConnectOptions,
AutocompletePreConnectOptions,
} from '../src/controller';
import fetchMock from 'fetch-mock-jest';
import userEvent from '@testing-library/user-event';
import TomSelect from 'tom-select';
import createFetchMock from 'vitest-fetch-mock';
import { vi } from 'vitest';

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

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

const fetchMocker = createFetchMock(vi);
describe('AutocompleteController', () => {
beforeAll(() => {
const application = Application.start();
application.register('autocomplete', AutocompleteController);

fetchMocker.enableMocks();
});

beforeEach(() => {
fetchMocker.resetMocks();
});

afterEach(() => {
document.body.innerHTML = '';

if (!fetchMock.done()) {
throw new Error('Mocked requests did not match');
}
fetchMock.reset();
});

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

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

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

// initial Ajax request on focus
fetchMock.mock(
'/path/to/autocomplete?query=',
fetchMock.mockResponseOnce(
JSON.stringify({
results: [
{
Expand All @@ -100,8 +103,7 @@ describe('AutocompleteController', () => {
}),
);

fetchMock.mock(
'/path/to/autocomplete?query=foo',
fetchMock.mockResponseOnce(
JSON.stringify({
results: [
{
Expand Down Expand Up @@ -132,6 +134,10 @@ describe('AutocompleteController', () => {
await waitFor(() => {
expect(container.querySelectorAll('.option[data-selectable]')).toHaveLength(2);
});

expect(fetchMock.requests().length).toEqual(2);
expect(fetchMock.requests()[0].url).toEqual('/path/to/autocomplete?query=');
expect(fetchMock.requests()[1].url).toEqual('/path/to/autocomplete?query=foo');
});

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

// initial Ajax request on focus
fetchMock.mock(
'/path/to/autocomplete?query=',
fetchMock.mockResponseOnce(
JSON.stringify({
results: [
{
Expand All @@ -158,8 +163,7 @@ describe('AutocompleteController', () => {
}),
);

fetchMock.mock(
'/path/to/autocomplete?query=foo',
fetchMock.mockResponseOnce(
JSON.stringify({
results: [
{
Expand Down Expand Up @@ -190,6 +194,10 @@ describe('AutocompleteController', () => {
await waitFor(() => {
expect(container.querySelectorAll('.option[data-selectable]')).toHaveLength(2);
});

expect(fetchMock.requests().length).toEqual(2);
expect(fetchMock.requests()[0].url).toEqual('/path/to/autocomplete?query=');
expect(fetchMock.requests()[1].url).toEqual('/path/to/autocomplete?query=foo');
});

it('limits updates when min-characters', async () => {
Expand All @@ -212,6 +220,8 @@ describe('AutocompleteController', () => {
await waitFor(() => {
expect(container.querySelectorAll('.option[data-selectable]')).toHaveLength(0);
});

expect(fetchMock.requests().length).toEqual(0);
});

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

// ajax call from initial focus
fetchMock.mock(
'/path/to/autocomplete?query=',
fetchMock.mockResponseOnce(
JSON.stringify({
results: [
{
Expand All @@ -267,8 +276,7 @@ describe('AutocompleteController', () => {
});

// now trigger a load
fetchMock.mock(
'/path/to/autocomplete?query=foo',
fetchMock.mockResponseOnce(
JSON.stringify({
results: [
{
Expand All @@ -290,8 +298,7 @@ describe('AutocompleteController', () => {
});

// now go below the min characters, but it should still load
fetchMock.mock(
'/path/to/autocomplete?query=fo',
fetchMock.mockResponseOnce(
JSON.stringify({
results: [
{
Expand All @@ -315,6 +322,11 @@ describe('AutocompleteController', () => {
await waitFor(() => {
expect(container.querySelectorAll('.option[data-selectable]')).toHaveLength(3);
});

expect(fetchMock.requests().length).toEqual(3);
expect(fetchMock.requests()[0].url).toEqual('/path/to/autocomplete?query=');
expect(fetchMock.requests()[1].url).toEqual('/path/to/autocomplete?query=foo');
expect(fetchMock.requests()[2].url).toEqual('/path/to/autocomplete?query=fo');
});

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

// initial Ajax request on focus
fetchMock.mock(
'/path/to/autocomplete?query=',
fetchMock.mockResponseOnce(
JSON.stringify({
results: [
{value: 1, text: 'dog1'},
Expand Down Expand Up @@ -390,8 +401,7 @@ describe('AutocompleteController', () => {
throw new Error('cannot find dropdown content element');
}

fetchMock.mock(
'/path/to/autocomplete?query=&page=2',
fetchMock.mockResponseOnce(
JSON.stringify({
results: [
{value: 11, text: 'dog11'},
Expand All @@ -407,6 +417,10 @@ describe('AutocompleteController', () => {
await waitFor(() => {
expect(container.querySelectorAll('.option[data-selectable]')).toHaveLength(12);
});

expect(fetchMock.requests().length).toEqual(2);
expect(fetchMock.requests()[0].url).toEqual('/path/to/autocomplete?query=');
expect(fetchMock.requests()[1].url).toEqual('/path/to/autocomplete?query=&page=2');
});

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

// initial Ajax request on focus with group_by options
fetchMock.mock(
'/path/to/autocomplete?query=',
fetchMock.mockResponseOnce(
JSON.stringify({
results: {
options: [
Expand Down Expand Up @@ -665,8 +678,7 @@ describe('AutocompleteController', () => {
}),
);

fetchMock.mock(
'/path/to/autocomplete?query=foo',
fetchMock.mockResponseOnce(
JSON.stringify({
results: {
options: [
Expand Down Expand Up @@ -709,5 +721,9 @@ describe('AutocompleteController', () => {
expect(container.querySelectorAll('.option[data-selectable]')).toHaveLength(2);
expect(container.querySelectorAll('.optgroup-header')).toHaveLength(1);
});

expect(fetchMock.requests().length).toEqual(2);
expect(fetchMock.requests()[0].url).toEqual('/path/to/autocomplete?query=');
expect(fetchMock.requests()[1].url).toEqual('/path/to/autocomplete?query=foo');
});
});
12 changes: 0 additions & 12 deletions src/Autocomplete/assets/test/setup.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/Chartjs/.gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
/phpunit.xml.dist export-ignore
/assets/src/**/*.ts export-ignore
/assets/test export-ignore
/assets/jest.config.js export-ignore
/assets/vitest.config.js export-ignore
/tests export-ignore
5 changes: 0 additions & 5 deletions src/Chartjs/assets/jest.config.js

This file was deleted.

4 changes: 2 additions & 2 deletions src/Chartjs/assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"@hotwired/stimulus": "^3.0.0",
"@types/chart.js": "^2.9.34",
"chart.js": "^3.4.1 <3.9",
"jest-canvas-mock": "^2.3.0",
"resize-observer-polyfill": "^1.5.1"
"resize-observer-polyfill": "^1.5.1",
"vitest-canvas-mock": "^0.3.3"
}
}
2 changes: 1 addition & 1 deletion src/Chartjs/assets/test/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@

'use strict';

import 'jest-canvas-mock';
import 'vitest-canvas-mock';
// eslint-disable-next-line
global.ResizeObserver = require('resize-observer-polyfill');
19 changes: 19 additions & 0 deletions src/Chartjs/assets/vitest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { defineConfig, mergeConfig } from 'vitest/config';
import configShared from '../../../vitest.config.js'
import path from 'path';

export default mergeConfig(
configShared,
defineConfig({
test: {
setupFiles: [path.join(__dirname, 'test', 'setup.js')],
deps: {
optimizer: {
web: {
include: ['vitest-canvas-mock'],
},
},
},
}
})
);
1 change: 0 additions & 1 deletion src/Cropperjs/.gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@
/phpunit.xml.dist export-ignore
/assets/src/**/*.ts export-ignore
/assets/test export-ignore
/assets/jest.config.js export-ignore
/tests export-ignore
1 change: 0 additions & 1 deletion src/Cropperjs/assets/jest.config.js

This file was deleted.

1 change: 0 additions & 1 deletion src/Dropzone/.gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@
/phpunit.xml.dist export-ignore
/assets/src/**/*.ts export-ignore
/assets/test export-ignore
/assets/jest.config.js export-ignore
/tests export-ignore
1 change: 0 additions & 1 deletion src/Dropzone/assets/jest.config.js

This file was deleted.

Loading

0 comments on commit 77b916a

Please sign in to comment.