Migrate from Jest to Vitest

This commit is contained in:
ChaosExAnima 2025-04-14 16:34:32 +02:00
parent fbe9728f36
commit 698962c185
No known key found for this signature in database
GPG Key ID: 8F2B333100FB6117
18 changed files with 2343 additions and 1928 deletions

View File

@ -40,4 +40,4 @@ jobs:
uses: ./.github/actions/setup-javascript uses: ./.github/actions/setup-javascript
- name: JavaScript testing - name: JavaScript testing
run: yarn jest --reporters github-actions summary run: yarn test:js

View File

@ -66,7 +66,7 @@ Example:
Pull requests that do not pass automated checks on CI may not be reviewed. In Pull requests that do not pass automated checks on CI may not be reviewed. In
particular, please keep in mind: particular, please keep in mind:
- Unit and integration tests (rspec, jest) - Unit and integration tests (rspec, vitest)
- Code style rules (rubocop, eslint) - Code style rules (rubocop, eslint)
- Normalization of locale files (i18n-tasks) - Normalization of locale files (i18n-tasks)
- Relevant accessibility or performance concerns - Relevant accessibility or performance concerns

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<AutosuggestEmoji /> renders emoji with custom url 1`] = ` exports[`<AutosuggestEmoji /> > renders emoji with custom url 1`] = `
<div <div
className="autosuggest-emoji" className="autosuggest-emoji"
> >
@ -17,7 +17,7 @@ exports[`<AutosuggestEmoji /> renders emoji with custom url 1`] = `
</div> </div>
`; `;
exports[`<AutosuggestEmoji /> renders native emoji 1`] = ` exports[`<AutosuggestEmoji /> > renders native emoji 1`] = `
<div <div
className="autosuggest-emoji" className="autosuggest-emoji"
> >

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<Avatar /> Autoplay renders a animated avatar 1`] = ` exports[`<Avatar /> > Autoplay > renders a animated avatar 1`] = `
<div <div
className="account__avatar account__avatar--loading" className="account__avatar account__avatar--loading"
onMouseEnter={[Function]} onMouseEnter={[Function]}
@ -21,7 +21,7 @@ exports[`<Avatar /> Autoplay renders a animated avatar 1`] = `
</div> </div>
`; `;
exports[`<Avatar /> Still renders a still avatar 1`] = ` exports[`<Avatar /> > Still > renders a still avatar 1`] = `
<div <div
className="account__avatar account__avatar--loading" className="account__avatar account__avatar--loading"
onMouseEnter={[Function]} onMouseEnter={[Function]}

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<AvatarOverlay renders a overlay avatar 1`] = ` exports[`<AvatarOverlay > renders a overlay avatar 1`] = `
<div <div
className="account__avatar-overlay" className="account__avatar-overlay"
onMouseEnter={[Function]} onMouseEnter={[Function]}

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<Button /> adds class "button-secondary" if props.secondary given 1`] = ` exports[`<Button /> > adds class "button-secondary" if props.secondary given 1`] = `
<button <button
className="button button-secondary" className="button button-secondary"
onClick={[Function]} onClick={[Function]}
@ -8,7 +8,7 @@ exports[`<Button /> adds class "button-secondary" if props.secondary given 1`] =
/> />
`; `;
exports[`<Button /> renders a button element 1`] = ` exports[`<Button /> > renders a button element 1`] = `
<button <button
className="button" className="button"
onClick={[Function]} onClick={[Function]}
@ -16,7 +16,7 @@ exports[`<Button /> renders a button element 1`] = `
/> />
`; `;
exports[`<Button /> renders a disabled attribute if props.disabled given 1`] = ` exports[`<Button /> > renders a disabled attribute if props.disabled given 1`] = `
<button <button
className="button" className="button"
disabled={true} disabled={true}
@ -25,7 +25,7 @@ exports[`<Button /> renders a disabled attribute if props.disabled given 1`] = `
/> />
`; `;
exports[`<Button /> renders class="button--block" if props.block given 1`] = ` exports[`<Button /> > renders class="button--block" if props.block given 1`] = `
<button <button
className="button button--block" className="button button--block"
onClick={[Function]} onClick={[Function]}
@ -33,7 +33,7 @@ exports[`<Button /> renders class="button--block" if props.block given 1`] = `
/> />
`; `;
exports[`<Button /> renders the children 1`] = ` exports[`<Button /> > renders the children 1`] = `
<button <button
className="button" className="button"
onClick={[Function]} onClick={[Function]}
@ -45,7 +45,7 @@ exports[`<Button /> renders the children 1`] = `
</button> </button>
`; `;
exports[`<Button /> renders the given text 1`] = ` exports[`<Button /> > renders the given text 1`] = `
<button <button
className="button" className="button"
onClick={[Function]} onClick={[Function]}
@ -55,7 +55,7 @@ exports[`<Button /> renders the given text 1`] = `
</button> </button>
`; `;
exports[`<Button /> renders the props.text instead of children 1`] = ` exports[`<Button /> > renders the props.text instead of children 1`] = `
<button <button
className="button" className="button"
onClick={[Function]} onClick={[Function]}

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<DisplayName /> renders display name + account name 1`] = ` exports[`<DisplayName /> > renders display name + account name 1`] = `
<span <span
className="display-name" className="display-name"
onMouseEnter={[Function]} onMouseEnter={[Function]}

View File

@ -21,7 +21,7 @@ describe('<Button />', () => {
}); });
it('handles click events using the given handler', () => { it('handles click events using the given handler', () => {
const handler = jest.fn(); const handler = vi.fn();
render(<Button onClick={handler}>button</Button>); render(<Button onClick={handler}>button</Button>);
fireEvent.click(screen.getByText('button')); fireEvent.click(screen.getByText('button'));
@ -29,7 +29,7 @@ describe('<Button />', () => {
}); });
it('does not handle click events if props.disabled given', () => { it('does not handle click events if props.disabled given', () => {
const handler = jest.fn(); const handler = vi.fn();
render(<Button onClick={handler} disabled>button</Button>); render(<Button onClick={handler} disabled>button</Button>);
fireEvent.click(screen.getByText('button')); fireEvent.click(screen.getByText('button'));

View File

@ -7,7 +7,7 @@ const fakeIcon = () => <span />;
describe('<Column />', () => { describe('<Column />', () => {
describe('<ColumnHeader /> click handler', () => { describe('<ColumnHeader /> click handler', () => {
it('runs the scroll animation if the column contains scrollable content', () => { it('runs the scroll animation if the column contains scrollable content', () => {
const scrollToMock = jest.fn(); const scrollToMock = vi.fn();
const { container } = render( const { container } = render(
<Column heading='notifications' icon='notifications' iconComponent={fakeIcon}> <Column heading='notifications' icon='notifications' iconComponent={fakeIcon}>
<div className='scrollable' /> <div className='scrollable' />

View File

@ -3,17 +3,17 @@ import { IntlProvider } from 'react-intl';
import { MemoryRouter } from 'react-router'; import { MemoryRouter } from 'react-router';
import type { RenderOptions } from '@testing-library/react'; import type { RenderOptions } from '@testing-library/react';
// eslint-disable-next-line import/no-extraneous-dependencies
import { render as rtlRender } from '@testing-library/react'; import { render as rtlRender } from '@testing-library/react';
import { IdentityContext } from './identity_context'; import { IdentityContext } from './identity_context';
beforeEach(() => { beforeAll(() => {
global.requestIdleCallback = jest global.requestIdleCallback = vi.fn((cb: IdleRequestCallback) => {
.fn() // @ts-expect-error IdleRequestCallback expects an argument of type IdleDeadline,
.mockImplementation((fn: () => void) => { // but that doesn't exist in this environment.
fn(); cb();
}); return 0;
});
}); });
function render( function render(
@ -46,7 +46,6 @@ function render(
} }
// re-export everything // re-export everything
// eslint-disable-next-line import/no-extraneous-dependencies
export * from '@testing-library/react'; export * from '@testing-library/react';
// override render method // override render method

View File

@ -1 +0,0 @@
import '@testing-library/jest-dom';

17
config/vite.json Normal file
View File

@ -0,0 +1,17 @@
{
"all": {
"sourceCodeDir": "app/javascript",
"additionalEntrypoints": ["~/{icons,images}/**/*", "~/styles/*.scss"],
"watchAdditionalPaths": []
},
"development": {
"autoBuild": true,
"publicOutputDir": "vite-dev",
"port": 3036
},
"test": {
"autoBuild": true,
"publicOutputDir": "vite-test",
"port": 3037
}
}

View File

@ -247,6 +247,7 @@ export default tseslint.config([
'config/webpack/**', 'config/webpack/**',
'app/javascript/mastodon/performance.js', 'app/javascript/mastodon/performance.js',
'app/javascript/mastodon/test_setup.js', 'app/javascript/mastodon/test_setup.js',
'app/javascript/mastodon/test_helpers.tsx',
'app/javascript/**/__tests__/**', 'app/javascript/**/__tests__/**',
], ],
}, },
@ -387,9 +388,7 @@ export default tseslint.config([
files: ['**/__tests__/*.js', '**/__tests__/*.jsx'], files: ['**/__tests__/*.js', '**/__tests__/*.jsx'],
languageOptions: { languageOptions: {
globals: { globals: globals.vitest,
...globals.jest,
},
}, },
}, },
]); ]);

View File

@ -1,28 +0,0 @@
/** @type {import('jest').Config} */
const config = {
testEnvironment: 'jsdom',
testPathIgnorePatterns: [
'<rootDir>/node_modules/',
'<rootDir>/vendor/',
'<rootDir>/config/',
'<rootDir>/log/',
'<rootDir>/public/',
'<rootDir>/tmp/',
],
setupFilesAfterEnv: ['<rootDir>/app/javascript/mastodon/test_setup.js'],
collectCoverageFrom: [
'app/javascript/mastodon/**/*.{js,jsx,ts,tsx}',
'!app/javascript/mastodon/features/emoji/emoji_compressed.js',
'!app/javascript/mastodon/service_worker/entry.js',
'!app/javascript/mastodon/test_setup.js',
],
// Those packages are ESM, so we need them to be processed by Babel
transformIgnorePatterns: ['/node_modules/(?!(redent|strip-indent)/)'],
coverageDirectory: '<rootDir>/coverage',
moduleDirectories: ['node_modules', '<rootDir>/app/javascript'],
moduleNameMapper: {
'\\.svg\\?react$': '<rootDir>/app/javascript/__mocks__/svg.js',
},
};
module.exports = config;

View File

@ -18,14 +18,14 @@
"format": "prettier --write --log-level warn .", "format": "prettier --write --log-level warn .",
"format:check": "prettier --check --ignore-unknown .", "format:check": "prettier --check --ignore-unknown .",
"i18n:extract": "formatjs extract 'app/javascript/**/*.{js,jsx,ts,tsx}' --ignore '**/*.d.ts' --out-file app/javascript/mastodon/locales/en.json --format config/formatjs-formatter.js", "i18n:extract": "formatjs extract 'app/javascript/**/*.{js,jsx,ts,tsx}' --ignore '**/*.d.ts' --out-file app/javascript/mastodon/locales/en.json --format config/formatjs-formatter.js",
"jest": "cross-env NODE_ENV=test jest",
"lint:js": "cd $INIT_CWD && eslint --cache --report-unused-disable-directives", "lint:js": "cd $INIT_CWD && eslint --cache --report-unused-disable-directives",
"lint:css": "stylelint \"**/*.{css,scss}\"", "lint:css": "stylelint \"**/*.{css,scss}\"",
"lint": "yarn lint:js && yarn lint:css", "lint": "yarn lint:js && yarn lint:css",
"postversion": "git push --tags", "postversion": "git push --tags",
"postinstall": "test -d node_modules/husky && husky || echo \"husky is not installed\"", "postinstall": "test -d node_modules/husky && husky || echo \"husky is not installed\"",
"start": "node ./streaming/index.js", "start": "node ./streaming/index.js",
"test": "yarn lint && yarn run typecheck && yarn jest", "test": "yarn lint && yarn run typecheck && yarn test:js run",
"test:js": "vitest",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"repository": { "repository": {
@ -127,6 +127,7 @@
"tiny-queue": "^0.2.1", "tiny-queue": "^0.2.1",
"twitter-text": "3.1.0", "twitter-text": "3.1.0",
"use-debounce": "^10.0.0", "use-debounce": "^10.0.0",
"vite": "^6.2.6",
"webpack": "^4.47.0", "webpack": "^4.47.0",
"webpack-assets-manifest": "^4.0.6", "webpack-assets-manifest": "^4.0.6",
"webpack-bundle-analyzer": "^4.8.0", "webpack-bundle-analyzer": "^4.8.0",
@ -152,7 +153,6 @@
"@types/hoist-non-react-statics": "^3.3.1", "@types/hoist-non-react-statics": "^3.3.1",
"@types/http-link-header": "^1.0.3", "@types/http-link-header": "^1.0.3",
"@types/intl": "^1.2.0", "@types/intl": "^1.2.0",
"@types/jest": "^29.5.2",
"@types/js-yaml": "^4.0.5", "@types/js-yaml": "^4.0.5",
"@types/lodash": "^4.14.195", "@types/lodash": "^4.14.195",
"@types/object-assign": "^4.0.30", "@types/object-assign": "^4.0.30",
@ -173,7 +173,7 @@
"@types/requestidlecallback": "^0.3.5", "@types/requestidlecallback": "^0.3.5",
"@types/webpack": "^4.41.33", "@types/webpack": "^4.41.33",
"@types/webpack-env": "^1.18.4", "@types/webpack-env": "^1.18.4",
"babel-jest": "^29.5.0", "@vitejs/plugin-react": "^4.2.1",
"eslint": "^9.23.0", "eslint": "^9.23.0",
"eslint-import-resolver-typescript": "^4.2.5", "eslint-import-resolver-typescript": "^4.2.5",
"eslint-plugin-formatjs": "^5.3.1", "eslint-plugin-formatjs": "^5.3.1",
@ -185,8 +185,6 @@
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"globals": "^16.0.0", "globals": "^16.0.0",
"husky": "^9.0.11", "husky": "^9.0.11",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"lint-staged": "^15.0.0", "lint-staged": "^15.0.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"react-test-renderer": "^18.2.0", "react-test-renderer": "^18.2.0",
@ -195,6 +193,9 @@
"stylelint-config-standard-scss": "^14.0.0", "stylelint-config-standard-scss": "^14.0.0",
"typescript": "~5.7.3", "typescript": "~5.7.3",
"typescript-eslint": "^8.28.0", "typescript-eslint": "^8.28.0",
"vite-plugin-rails": "^0.5.0",
"vite-plugin-svgr": "^4.2.0",
"vitest": "^3.1.1",
"webpack-dev-server": "^3.11.3" "webpack-dev-server": "^3.11.3"
}, },
"resolutions": { "resolutions": {

View File

@ -11,16 +11,20 @@
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"esModuleInterop": true, "esModuleInterop": true,
"skipLibCheck": true, "skipLibCheck": true,
"types": ["vitest/globals", "@types/webpack-env"],
"baseUrl": "./", "baseUrl": "./",
"incremental": true, "incremental": true,
"tsBuildInfoFile": "tmp/cache/tsconfig.tsbuildinfo", "tsBuildInfoFile": "tmp/cache/tsconfig.tsbuildinfo",
"paths": { "paths": {
"@/*": ["app/javascript/*"],
"mastodon": ["app/javascript/mastodon"], "mastodon": ["app/javascript/mastodon"],
"mastodon/*": ["app/javascript/mastodon/*"], "mastodon/*": ["app/javascript/mastodon/*"],
"@/*": ["app/javascript/*"] "images/*": ["app/javascript/images/*"],
"styles/*": ["app/javascript/styles/*"]
} }
}, },
"include": [ "include": [
"vite.config.mts",
"app/javascript/mastodon", "app/javascript/mastodon",
"app/javascript/entrypoints", "app/javascript/entrypoints",
"app/javascript/types" "app/javascript/types"

58
vite.config.mts Normal file
View File

@ -0,0 +1,58 @@
/// <reference types="vitest" />
import fs from 'fs';
import path from 'path';
import react from '@vitejs/plugin-react';
import RailsPlugin from 'vite-plugin-rails';
import svgr from 'vite-plugin-svgr';
import { defineConfig, configDefaults } from 'vitest/config';
const sourceCodeDir = 'app/javascript';
const items = fs.readdirSync(sourceCodeDir);
const directories = items.filter((item) =>
fs.lstatSync(path.join(sourceCodeDir, item)).isDirectory(),
);
const aliasesFromJavascriptRoot: Record<string, string> = {};
directories.forEach((directory) => {
aliasesFromJavascriptRoot[directory] = path.resolve(
__dirname,
sourceCodeDir,
directory,
);
});
export default defineConfig({
resolve: {
alias: {
...aliasesFromJavascriptRoot,
},
},
plugins: [
RailsPlugin(),
react({
include: ['**/*.jsx', '**/*.tsx'],
babel: {
plugins: ['formatjs', 'preval', 'transform-react-remove-prop-types'],
},
}),
svgr(),
],
test: {
environment: 'jsdom',
include: [
...configDefaults.include,
'**/__tests__/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
],
exclude: [
...configDefaults.exclude,
'**/node_modules/**',
'vendor/**',
'config/**',
'log/**',
'public/**',
'tmp/**',
],
globals: true,
},
});

4082
yarn.lock

File diff suppressed because it is too large Load Diff