diff --git a/.circleci/config.yml b/.circleci/config.yml index b1e2963717b6f..d8f74b0a57c09 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -26,16 +26,19 @@ parameters: type: string default: '' +default-parameters: &default-parameters + react-version: + description: The version of react to be used + type: string + default: << pipeline.parameters.react-version >> + e2e-base-url: + description: The base url for running end-to-end test + type: string + default: << pipeline.parameters.e2e-base-url >> + default-job: &default-job parameters: - react-version: - description: The version of react to be used - type: string - default: << pipeline.parameters.react-version >> - e2e-base-url: - description: The base url for running end-to-end test - type: string - default: << pipeline.parameters.e2e-base-url >> + <<: *default-parameters environment: # expose it globally otherwise we have to thread it from each job to the install command BROWSERSTACK_FORCE: << pipeline.parameters.browserstack-force >> @@ -150,21 +153,46 @@ jobs: - install_js: react-version: << parameters.react-version >> - run: - name: Tests fake browser - command: pnpm test:coverage - - run: - name: Check coverage generated - command: | - if ! [[ -s coverage/lcov.info ]] - then - exit 1 - fi - - run: - name: Coverage - command: | - curl -Os https://uploader.codecov.io/latest/linux/codecov - chmod +x codecov - ./codecov -t ${CODECOV_TOKEN} -Z -F "$REACT_VERSION-jsdom" + name: Test JSDOM + command: pnpm test:unit:jsdom + + test_browser: + <<: *default-job + resource_class: medium+ + steps: + - checkout + - install_js: + playwright: true + react-version: << parameters.react-version >> + - when: + condition: + not: + equal: ['stable', << parameters.react-version >>] + steps: + - run: + name: Test Browser + command: pnpm test:unit:browser + - when: + condition: + equal: ['stable', << parameters.react-version >>] + steps: + - run: + name: Test Browser + Coverage + command: pnpm test:unit:browser --coverage + - run: + name: Check coverage generated + command: | + if ! [[ -s coverage/lcov.info ]] + then + exit 1 + fi + - run: + name: Coverage + command: | + curl -Os https://uploader.codecov.io/latest/linux/codecov + chmod +x codecov + ./codecov -t ${CODECOV_TOKEN} -Z -F "$REACT_VERSION-browser" + test_lint: <<: *default-job steps: @@ -242,21 +270,6 @@ jobs: else exit 0 fi - - test_browser: - <<: *default-job - steps: - - checkout - - install_js: - playwright: true - react-version: << parameters.react-version >> - - run: - name: Tests real browsers - command: pnpm test:karma - - store_artifacts: - # hardcoded in karma-webpack - path: /tmp/_karma_webpack_ - destination: artifact-file test_types: <<: *default-job steps: @@ -353,9 +366,7 @@ workflows: jobs: - checkout: <<: *default-context - - test_e2e_website: - requires: - - checkout + - test_e2e_website additional-tests: when: diff --git a/babel.config.js b/babel.config.js index 5733bc2f8c2d5..65648d11bec51 100644 --- a/babel.config.js +++ b/babel.config.js @@ -105,30 +105,6 @@ module.exports = function getBabelConfig(api) { ], ]; - if (process.env.NODE_ENV === 'test') { - plugins.push(['@babel/plugin-transform-export-namespace-from']); - // We replace `date-fns` imports with an aliased `date-fns@v2` version installed as `date-fns-v2` for tests. - plugins.push([ - 'babel-plugin-replace-imports', - { - test: /date-fns/i, - replacer: 'date-fns-v2', - // This option is provided by the `patches/babel-plugin-replace-imports@1.0.2.patch` patch - filenameIncludes: 'src/AdapterDateFnsV2/', - }, - ]); - plugins.push([ - 'babel-plugin-replace-imports', - { - test: /date-fns-jalali/i, - replacer: 'date-fns-jalali-v2', - // This option is provided by the `patches/babel-plugin-replace-imports@1.0.2.patch` patch - filenameIncludes: 'src/AdapterDateFnsJalaliV2/', - }, - 'replace-date-fns-jalali-imports', - ]); - } - if (process.env.NODE_ENV === 'production') { if (!process.env.TEST_BUILD) { plugins.push(['babel-plugin-react-remove-properties', { properties: ['data-testid'] }]); diff --git a/docs/tsconfig.json b/docs/tsconfig.json index 98525baba2518..6bcba22de4a80 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -9,7 +9,8 @@ "resolveJsonModule": true, "skipLibCheck": true, "esModuleInterop": true, - "incremental": true + "incremental": true, + "types": ["@mui/internal-test-utils/initMatchers", "chai-dom", "mocha"] }, "include": ["next-env.d.ts", "next.config.mjs", "docs-env.d.ts", "src", "pages/**/*.ts*", "data"], "exclude": ["docs/.next", "docs/export", "pages/playground"] diff --git a/docs/vitest.config.jsdom.mts b/docs/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..4bb19fe0b4df8 --- /dev/null +++ b/docs/vitest.config.jsdom.mts @@ -0,0 +1,13 @@ +import { mergeConfig, defineProject } from 'vitest/config'; +import sharedConfig from '../vitest.shared.mts'; +import { getTestName } from '../scripts/getTestName.mts'; + +export default mergeConfig( + sharedConfig, + defineProject({ + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + }, + }), +); diff --git a/mui-env.d.ts b/mui-env.d.ts index 8243f45871615..ddca7e0890a24 100644 --- a/mui-env.d.ts +++ b/mui-env.d.ts @@ -1,11 +1,15 @@ export {}; // Ensure this file is treated as a module to avoid global scope TS error declare global { + interface MUIEnv { + NODE_ENV?: string; + } + + interface Process { + env: MUIEnv; + } + // support process.env.NODE_ENV === '...' // @ts-ignore - const process: { - env: { - NODE_ENV?: string; - }; - }; + const process: Process; } diff --git a/package.json b/package.json index 2cbfa09f0be39..b32d3e3d6b158 100644 --- a/package.json +++ b/package.json @@ -32,14 +32,12 @@ "proptypes": "tsx ./docs/scripts/generateProptypes.ts", "size:snapshot": "node --max-old-space-size=2048 ./scripts/sizeSnapshot/create", "size:why": "pnpm size:snapshot --analyze --accurateBundles", - "tc": "node test/cli.js", - "test": "node scripts/test.mjs", - "test:coverage": "cross-env NODE_OPTIONS=--max-old-space-size=4096 NODE_ENV=test TZ=UTC BABEL_ENV=coverage nyc mocha 'packages/**/*.test.{js,ts,tsx}' 'docs/**/*.test.{js,ts,tsx}' && nyc report -r lcovonly", - "test:coverage:html": "cross-env NODE_ENV=test TZ=UTC BABEL_ENV=coverage nyc mocha 'packages/**/*.test.{js,ts,tsx}' 'docs/**/*.test.{js,ts,tsx}' && nyc report --reporter=html", - "test:coverage:inspect": "cross-env NODE_ENV=test TZ=UTC BABEL_ENV=coverage mocha --inspect-brk", - "test:karma": "cross-env NODE_ENV=test TZ=UTC karma start test/karma.conf.js", - "test:karma:parallel": "cross-env NODE_ENV=test TZ=UTC PARALLEL=true karma start test/karma.conf.js", - "test:unit": "cross-env NODE_ENV=test TZ=UTC mocha -n expose_gc 'packages/**/*.test.{js,ts,tsx}' 'docs/**/*.test.{js,ts,tsx}'", + "test": "pnpm test:unit:jsdom", + "test:jsdom": "pnpm test:unit:jsdom", + "test:unit": "pnpm test:unit:jsdom", + "test:unit:jsdom": "cross-env NODE_ENV=test TZ=UTC vitest", + "test:browser": "pnpm test:unit:browser", + "test:unit:browser": "cross-env NODE_ENV=test TZ=UTC BROWSER=true vitest", "test:e2e": "pnpm run release:build && cd test/e2e && pnpm run start", "test:e2e-website": "npx playwright test test/e2e-website --config test/e2e-website/playwright.config.ts", "test:e2e-website:dev": "PLAYWRIGHT_TEST_BASE_URL=http://localhost:3001 npx playwright test test/e2e-website --config test/e2e-website/playwright.config.ts", @@ -99,7 +97,6 @@ "@playwright/test": "^1.52.0", "@types/babel__core": "^7.20.5", "@types/babel__traverse": "^7.20.7", - "@types/chai": "^4.3.20", "@types/chai-dom": "^1.11.3", "@types/fs-extra": "^11.0.4", "@types/karma": "^6.3.9", @@ -113,6 +110,9 @@ "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^8.31.1", "@typescript-eslint/parser": "^8.31.1", + "@vitejs/plugin-react": "^4.3.2", + "@vitest/browser": "^3.1.2", + "@vitest/coverage-v8": "^3.1.2", "@vvago/vale": "^3.11.2", "autoprefixer": "^10.4.21", "axe-core": "4.10.3", @@ -121,7 +121,6 @@ "babel-plugin-module-resolver": "^5.0.2", "babel-plugin-optimize-clsx": "^2.6.2", "babel-plugin-react-remove-properties": "^0.3.0", - "babel-plugin-replace-imports": "^1.0.2", "babel-plugin-search-and-replace": "^1.1.1", "babel-plugin-transform-inline-environment-variables": "^0.4.4", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", @@ -135,6 +134,7 @@ "danger": "^13.0.4", "date-fns-jalali-v2": "npm:date-fns-jalali@2.30.0-0", "date-fns-v2": "npm:date-fns@2.30.0", + "esbuild": "^0.25.3", "eslint": "^8.57.1", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-base": "^15.0.0", @@ -169,6 +169,7 @@ "karma-webpack": "^5.0.1", "lerna": "^8.2.2", "lodash": "^4.17.21", + "magic-string": "^0.30.17", "markdownlint-cli2": "^0.17.2", "mocha": "^11.2.2", "moment": "^2.30.1", @@ -192,6 +193,8 @@ "unist-util-visit": "^5.0.0", "util": "^0.12.5", "vite": "^6.3.4", + "vitest": "3.1.2", + "vitest-fail-on-console": "^0.7.1", "webpack": "^5.99.7", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^6.0.1", @@ -204,8 +207,7 @@ }, "pnpm": { "patchedDependencies": { - "karma-mocha@2.0.1": "patches/karma-mocha@2.0.1.patch", - "babel-plugin-replace-imports@1.0.2": "patches/babel-plugin-replace-imports@1.0.2.patch" + "karma-mocha@2.0.1": "patches/karma-mocha@2.0.1.patch" }, "onlyBuiltDependencies": [ "@swc/core", diff --git a/packages/x-charts-pro/src/BarChartPro/BarChartPro.zoom.test.tsx b/packages/x-charts-pro/src/BarChartPro/BarChartPro.zoom.test.tsx index 79ca4d43bdf2e..09870b3309df2 100644 --- a/packages/x-charts-pro/src/BarChartPro/BarChartPro.zoom.test.tsx +++ b/packages/x-charts-pro/src/BarChartPro/BarChartPro.zoom.test.tsx @@ -64,8 +64,7 @@ describeSkipIf(isJSDOM)(' - Zoom', () => { } }); - it('should zoom on wheel', async function test() { - this.timeout(10000); + it('should zoom on wheel', async () => { const onZoomChange = sinon.spy(); const { user } = render( , diff --git a/packages/x-charts-pro/src/LineChartPro/LineChartPro.zoom.test.tsx b/packages/x-charts-pro/src/LineChartPro/LineChartPro.zoom.test.tsx index 4dea98967b505..b90877f808c02 100644 --- a/packages/x-charts-pro/src/LineChartPro/LineChartPro.zoom.test.tsx +++ b/packages/x-charts-pro/src/LineChartPro/LineChartPro.zoom.test.tsx @@ -65,8 +65,7 @@ describeSkipIf(isJSDOM)(' - Zoom', () => { } }); - it('should zoom on wheel', async function test() { - this.timeout(10000); + it('should zoom on wheel', async () => { const onZoomChange = sinon.spy(); const { user } = render( , diff --git a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.zoom.test.tsx b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.zoom.test.tsx index 38c93248a6dd3..acd30e8db5aa5 100644 --- a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.zoom.test.tsx +++ b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.zoom.test.tsx @@ -91,8 +91,7 @@ describeSkipIf(isJSDOM)(' - Zoom', () => { } }); - it('should zoom on wheel', async function test() { - this.timeout(10000); + it('should zoom on wheel', async () => { const onZoomChange = sinon.spy(); const { user } = render( , diff --git a/packages/x-charts-pro/vitest.config.browser.mts b/packages/x-charts-pro/vitest.config.browser.mts new file mode 100644 index 0000000000000..1b6f538191a3a --- /dev/null +++ b/packages/x-charts-pro/vitest.config.browser.mts @@ -0,0 +1,19 @@ +/// +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'browser', + browser: { + enabled: true, + instances: [ + { + browser: 'chromium', + }, + ], + }, + }, +}); diff --git a/packages/x-charts-pro/vitest.config.jsdom.mts b/packages/x-charts-pro/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..50dd48e254699 --- /dev/null +++ b/packages/x-charts-pro/vitest.config.jsdom.mts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + }, +}); diff --git a/packages/x-charts-vendor/vitest.config.browser.mts b/packages/x-charts-vendor/vitest.config.browser.mts new file mode 100644 index 0000000000000..1b6f538191a3a --- /dev/null +++ b/packages/x-charts-vendor/vitest.config.browser.mts @@ -0,0 +1,19 @@ +/// +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'browser', + browser: { + enabled: true, + instances: [ + { + browser: 'chromium', + }, + ], + }, + }, +}); diff --git a/packages/x-charts-vendor/vitest.config.jsdom.mts b/packages/x-charts-vendor/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..50dd48e254699 --- /dev/null +++ b/packages/x-charts-vendor/vitest.config.jsdom.mts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + }, +}); diff --git a/packages/x-charts/vitest.config.browser.mts b/packages/x-charts/vitest.config.browser.mts new file mode 100644 index 0000000000000..1b6f538191a3a --- /dev/null +++ b/packages/x-charts/vitest.config.browser.mts @@ -0,0 +1,19 @@ +/// +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'browser', + browser: { + enabled: true, + instances: [ + { + browser: 'chromium', + }, + ], + }, + }, +}); diff --git a/packages/x-charts/vitest.config.jsdom.mts b/packages/x-charts/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..50dd48e254699 --- /dev/null +++ b/packages/x-charts/vitest.config.jsdom.mts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + }, +}); diff --git a/packages/x-codemod/vitest.config.jsdom.mts b/packages/x-codemod/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..50dd48e254699 --- /dev/null +++ b/packages/x-codemod/vitest.config.jsdom.mts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + }, +}); diff --git a/packages/x-data-grid-premium/vitest.config.browser.mts b/packages/x-data-grid-premium/vitest.config.browser.mts new file mode 100644 index 0000000000000..1b6f538191a3a --- /dev/null +++ b/packages/x-data-grid-premium/vitest.config.browser.mts @@ -0,0 +1,19 @@ +/// +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'browser', + browser: { + enabled: true, + instances: [ + { + browser: 'chromium', + }, + ], + }, + }, +}); diff --git a/packages/x-data-grid-premium/vitest.config.jsdom.mts b/packages/x-data-grid-premium/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..50dd48e254699 --- /dev/null +++ b/packages/x-data-grid-premium/vitest.config.jsdom.mts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + }, +}); diff --git a/packages/x-data-grid-pro/src/tests/columnHeaders.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/columnHeaders.DataGridPro.test.tsx index 50c2f9892b1f4..60a4d5b3d6eeb 100644 --- a/packages/x-data-grid-pro/src/tests/columnHeaders.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/columnHeaders.DataGridPro.test.tsx @@ -5,7 +5,7 @@ import { expect } from 'chai'; import { gridClasses, DataGridPro, DataGridProProps } from '@mui/x-data-grid-pro'; import { getColumnHeaderCell, getColumnValues } from 'test/utils/helperFn'; import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; -import { SinonFakeTimers, useFakeTimers } from 'sinon'; +import { vi } from 'vitest'; describe(' - Column headers', () => { const { render } = createRenderer(); @@ -51,15 +51,12 @@ describe(' - Column headers', () => { }); describe('GridColumnHeaderMenu', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; - beforeEach(() => { - timer = useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); it('should close the menu when the window is scrolled', async () => { @@ -71,11 +68,11 @@ describe(' - Column headers', () => { const columnCell = getColumnHeaderCell(0); const menuIconButton = columnCell.querySelector('button[aria-label="brand column menu"]')!; fireEvent.click(menuIconButton); - await act(() => timer?.runAll()); + await act(async () => vi.runAllTimersAsync()); expect(screen.queryByRole('menu')).not.to.equal(null); const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; fireEvent.wheel(virtualScroller); - await act(() => timer?.runAll()); + await act(async () => vi.runAllTimersAsync()); expect(screen.queryByRole('menu')).to.equal(null); }); @@ -91,10 +88,10 @@ describe(' - Column headers', () => { const columnCell = getColumnHeaderCell(0); const menuIconButton = columnCell.querySelector('button[aria-label="brand column menu"]')!; fireEvent.click(menuIconButton); - await act(() => timer?.runAll()); + await act(async () => vi.runAllTimersAsync()); expect(screen.queryByRole('menu')).not.to.equal(null); setProps({ rows: [...baselineProps.rows] }); - await act(() => timer?.runAll()); + await act(async () => vi.runAllTimersAsync()); expect(screen.queryByRole('menu')).not.to.equal(null); }); @@ -108,7 +105,7 @@ describe(' - Column headers', () => { const columnCell = getColumnHeaderCell(0); const menuIconButton = columnCell.querySelector('button[aria-label="brand column menu"]')!; fireEvent.click(menuIconButton); - await act(() => timer?.runAll()); + await act(async () => vi.runAllTimersAsync()); expect(screen.queryByRole('menu')).not.to.equal(null); fireEvent.click(screen.getByRole('menu')); expect(getColumnValues(0)).to.deep.equal(['Nike', 'Adidas', 'Puma']); @@ -124,7 +121,7 @@ describe(' - Column headers', () => { const menuIconButton = columnCell.querySelector('button[aria-label="brand column menu"]')!; expect(getColumnValues(0)).to.deep.equal(['Nike', 'Adidas', 'Puma']); fireEvent.click(menuIconButton); - await act(() => timer?.runAll()); + await act(async () => vi.runAllTimersAsync()); expect(screen.queryByRole('menu')).not.to.equal(null); fireEvent.click(screen.getByRole('menuitem', { name: 'Sort by ASC' })); expect(getColumnValues(0)).to.deep.equal(['Adidas', 'Nike', 'Puma']); @@ -147,14 +144,14 @@ describe(' - Column headers', () => { const menuIconButton = columnCell.querySelector('button[aria-label="brand column menu"]')!; fireEvent.click(menuIconButton); - await act(() => timer?.runAll()); + await act(async () => vi.runAllTimersAsync()); expect(screen.queryByRole('menu')).not.to.equal(null); const separator = columnCell.querySelector('.MuiDataGrid-iconSeparator')!; fireEvent.mouseDown(separator); // TODO remove mouseUp once useGridColumnReorder will handle cleanup properly fireEvent.mouseUp(separator); - await act(() => timer?.runAll()); + await act(async () => vi.runAllTimersAsync()); expect(screen.queryByRole('menu')).to.equal(null); }); @@ -179,18 +176,18 @@ describe(' - Column headers', () => { )!; fireEvent.click(menuIconButton); - await act(() => timer?.runAll()); + await act(async () => vi.runAllTimersAsync()); expect(screen.queryByRole('menu')).not.to.equal(null); const separator = columnToResizeCell.querySelector( `.${gridClasses['columnSeparator--resizable']}`, )!; fireEvent.mouseDown(separator); - await act(() => timer?.runAll()); + await act(async () => vi.runAllTimersAsync()); expect(screen.queryByRole('menu')).to.equal(null); // cleanup fireEvent.mouseUp(separator); - await act(() => timer?.runAll()); + await act(async () => vi.runAllTimersAsync()); }); it('should close the menu of a column when pressing the Escape key', async () => { @@ -204,11 +201,11 @@ describe(' - Column headers', () => { const menuIconButton = columnCell.querySelector('button[aria-label="brand column menu"]')!; fireEvent.click(menuIconButton); - await act(() => timer?.runAll()); + await act(async () => vi.runAllTimersAsync()); expect(screen.queryByRole('menu')).not.to.equal(null); /* eslint-disable material-ui/disallow-active-element-as-key-event-target */ fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); - await act(() => timer?.runAll()); + await act(async () => vi.runAllTimersAsync()); expect(screen.queryByRole('menu')).to.equal(null); }); @@ -226,10 +223,10 @@ describe(' - Column headers', () => { const menuIconButton = columnCell.querySelector('button[aria-label="brand column menu"]')!; fireEvent.click(menuIconButton); expect(menuIconButton?.parentElement).to.have.class(gridClasses.menuOpen); - await act(() => timer?.runAll()); // Wait for the transition to run + await act(async () => vi.runAllTimersAsync()); // Wait for the transition to run fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); expect(menuIconButton?.parentElement).to.have.class(gridClasses.menuOpen); - await act(() => timer?.runAll()); // Wait for the transition to run + await act(async () => vi.runAllTimersAsync()); // Wait for the transition to run expect(menuIconButton?.parentElement).not.to.have.class(gridClasses.menuOpen); // restore previous config @@ -253,7 +250,7 @@ describe(' - Column headers', () => { const columnCell = getColumnHeaderCell(0); const menuIconButton = columnCell.querySelector('button[aria-label="brand column menu"]')!; fireEvent.click(menuIconButton); - await act(() => timer?.runAll()); + await act(async () => vi.runAllTimersAsync()); const menu = screen.getByRole('menu'); const descMenuitem = screen.getByRole('menuitem', { name: /sort by desc/i }); @@ -277,7 +274,7 @@ describe(' - Column headers', () => { const columnCell = getColumnHeaderCell(0); const menuIconButton = columnCell.querySelector('button[aria-label="brand column menu"]')!; fireEvent.click(menuIconButton); - await act(() => timer?.runAll()); + await act(async () => vi.runAllTimersAsync()); const menu = screen.getByRole('menu'); expect(menu).toHaveFocus(); diff --git a/packages/x-data-grid-pro/src/tests/columns.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/columns.DataGridPro.test.tsx index 738c07057675b..1592f49e493b7 100644 --- a/packages/x-data-grid-pro/src/tests/columns.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/columns.DataGridPro.test.tsx @@ -35,7 +35,7 @@ describe(' - Columns', () => { function Test(props: Partial) { apiRef = useGridApiRef(); return ( -
+
); @@ -610,7 +610,7 @@ describe(' - Columns', () => { await user.dblClick(separators[0]); await waitFor(() => { - expect(columns.map((_, i) => getColumnHeaderCell(i).offsetWidth)).to.deep.equal([50, 233]); + expect(columns.map((_, i) => getColumnHeaderCell(i).offsetWidth)).to.deep.equal([50, 248]); }); await user.dblClick(separators[1]); @@ -625,7 +625,9 @@ describe(' - Columns', () => { await act(async () => apiRef.current?.autosizeColumns({ includeHeaders: false, ...options }), ); - expect(getWidths()).to.deep.equal(widths); + await waitFor(() => { + expect(getWidths()).to.deep.equal(widths); + }); }; it('.columns works', async () => { @@ -646,7 +648,7 @@ describe(' - Columns', () => { it('.expand works', async () => { // These values are tuned to Ubuntu/Chromium and might be flaky in other environments - await autosize({ expand: true }, [135, 147]); + await autosize({ expand: true }, [142, 155]); }); }); }); diff --git a/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx index dca905e2fc91a..0a636b5bb96b2 100644 --- a/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx @@ -501,9 +501,7 @@ describe(' - Filter', () => { // The first combo is hidden and we include hidden elements to make the query faster // https://github.com/testing-library/dom-testing-library/issues/820#issuecomment-726936225 const input = getSelectInput( - screen.queryAllByRole('combobox', { name: 'Logic operator', hidden: true })[ - isJSDOM ? 1 : 0 // https://github.com/testing-library/dom-testing-library/issues/846 - ], + screen.queryAllByRole('combobox', { name: 'Logic operator', hidden: true })[0], ); fireEvent.change(input!, { target: { value: 'or' } }); expect(onFilterModelChange.callCount).to.equal(1); @@ -1362,8 +1360,7 @@ describe(' - Filter', () => { }, }; render(); - // For JSDom, the first hidden combo is also found which we are not interested in - const select = screen.getAllByRole('combobox', { name: 'Logic operator' })[isJSDOM ? 1 : 0]; + const select = screen.getAllByRole('combobox', { name: 'Logic operator' })[0]; expect(select).not.to.have.class('Mui-disabled'); }); diff --git a/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx index 0195ccd1aa909..80f090f74e173 100644 --- a/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { SinonFakeTimers, useFakeTimers, spy } from 'sinon'; +import { spy } from 'sinon'; import { RefObject } from '@mui/x-internals/types'; import { GridApi, @@ -17,6 +17,7 @@ import { getBasicGridData } from '@mui/x-data-grid-generator'; import { createRenderer, fireEvent, act, screen, waitFor } from '@mui/internal-test-utils'; import { getCell, getRow, spyApi } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; +import { vi } from 'vitest'; describe(' - Row editing', () => { const { render } = createRenderer(); @@ -409,15 +410,12 @@ describe(' - Row editing', () => { }); describe('with debounceMs > 0', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; - beforeEach(() => { - timer = useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); it('should debounce multiple changes if debounceMs > 0', async () => { @@ -447,7 +445,7 @@ describe(' - Row editing', () => { expect(renderEditCell.callCount).to.equal(0); await act(async () => { - await timer?.tickAsync(100); + await vi.advanceTimersByTimeAsync(100); }); expect(renderEditCell.callCount).not.to.equal(0); expect(renderEditCell.lastCall.args[0].value).to.equal('USD GBP'); diff --git a/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx index 31521fa6aed90..46e27758f3680 100644 --- a/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; import { createRenderer, act, fireEvent, waitFor, reactMajor } from '@mui/internal-test-utils'; -import { SinonFakeTimers, useFakeTimers, spy } from 'sinon'; +import { spy } from 'sinon'; import { expect } from 'chai'; +import { vi } from 'vitest'; import { RefObject } from '@mui/x-internals/types'; import { $, @@ -176,15 +177,12 @@ describe(' - Rows', () => { } describe('throttling', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; - beforeEach(() => { - timer = useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); it('should not throttle by default', () => { @@ -201,15 +199,15 @@ describe(' - Rows', () => { await act(async () => apiRef.current?.updateRows([{ id: 1, brand: 'Fila' }])); await act(async () => { - await timer?.tickAsync(10); + await vi.advanceTimersByTimeAsync(10); }); expect(getColumnValues(0)).to.deep.equal(['Nike', 'Adidas', 'Puma']); await act(async () => { - await timer?.tickAsync(100); + await vi.advanceTimersByTimeAsync(100); }); - - timer?.restore(); + // It seems that the trigger is not dependant only on timeout. + vi.useRealTimers(); await waitFor(async () => { expect(getColumnValues(0)).to.deep.equal(['Nike', 'Fila', 'Puma']); }); @@ -376,15 +374,12 @@ describe(' - Rows', () => { } describe('throttling', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; - beforeEach(() => { - timer = useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); it('should not throttle by default', () => { @@ -401,20 +396,20 @@ describe(' - Rows', () => { await act(() => apiRef.current?.setRows([{ id: 3, brand: 'Asics' }])); await act(async () => { - await timer?.tickAsync(10); + await vi.advanceTimersByTimeAsync(10); }); expect(getColumnValues(0)).to.deep.equal(['Nike', 'Adidas', 'Puma']); // React 18 seems to render twice const timerCount = reactMajor < 19 ? 2 : 1; - expect(timer?.countTimers()).to.equal(timerCount); + expect(vi.getTimerCount()).to.equal(timerCount); await act(async () => { - await timer?.tickAsync(100); + await vi.advanceTimersByTimeAsync(100); }); - expect(timer?.countTimers()).to.equal(0); + expect(vi.getTimerCount()).to.equal(0); // It seems that the trigger is not dependant only on timeout. - timer?.restore(); + vi.useRealTimers(); await waitFor(async () => { expect(getColumnValues(0)).to.deep.equal(['Asics']); }); @@ -577,8 +572,8 @@ describe(' - Rows', () => { let firstColumn = $$(firstRow, '[role="gridcell"]')[0]; expect(firstColumn).to.have.attr('data-colindex', '0'); await act(async () => virtualScroller.scrollTo({ left: columnThresholdPx })); - firstColumn = $(renderingZone, '[role="row"] > [role="gridcell"]')!; await waitFor(() => { + firstColumn = $(renderingZone, '[role="row"] > [role="gridcell"]')!; expect(firstColumn).to.have.attr('data-colindex', '1'); }); }); diff --git a/packages/x-data-grid-pro/tsconfig.json b/packages/x-data-grid-pro/tsconfig.json index a95beb1e8020a..08e4c0e2576ce 100644 --- a/packages/x-data-grid-pro/tsconfig.json +++ b/packages/x-data-grid-pro/tsconfig.json @@ -7,7 +7,8 @@ "chai-dom", "mocha", "node" - ] + ], + "skipLibCheck": true }, "include": ["src/**/*"] } diff --git a/packages/x-data-grid-pro/vitest.config.browser.mts b/packages/x-data-grid-pro/vitest.config.browser.mts new file mode 100644 index 0000000000000..8362de2857660 --- /dev/null +++ b/packages/x-data-grid-pro/vitest.config.browser.mts @@ -0,0 +1,23 @@ +/// +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'browser', + browser: { + enabled: true, + instances: [ + { + browser: 'chromium', + launch: { + // Required for tests which use scrollbars. + ignoreDefaultArgs: ['--hide-scrollbars'], + }, + }, + ], + }, + }, +}); diff --git a/packages/x-data-grid-pro/vitest.config.jsdom.mts b/packages/x-data-grid-pro/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..50dd48e254699 --- /dev/null +++ b/packages/x-data-grid-pro/vitest.config.jsdom.mts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + }, +}); diff --git a/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx b/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx index 39d8927ad02d5..ed148d9e434e4 100644 --- a/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx @@ -953,7 +953,7 @@ describe(' - Layout & warnings', () => { 'The Data Grid component requires all rows to have a unique `id` property', reactMajor < 19 && 'The Data Grid component requires all rows to have a unique `id` property', - reactMajor < 19 && 'The above error occurred in the component', + reactMajor < 19 && 'The above error occurred in the component', ]); expect((errorRef.current as any).errors).to.have.length(1); expect((errorRef.current as any).errors[0].toString()).to.include( diff --git a/packages/x-data-grid/src/tests/quickFiltering.DataGrid.test.tsx b/packages/x-data-grid/src/tests/quickFiltering.DataGrid.test.tsx index 6a171cf373681..bddd0f4c77ac6 100644 --- a/packages/x-data-grid/src/tests/quickFiltering.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/quickFiltering.DataGrid.test.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { createRenderer, screen, fireEvent, reactMajor } from '@mui/internal-test-utils'; +import { createRenderer, screen, reactMajor, waitFor, act } from '@mui/internal-test-utils'; import { expect } from 'chai'; import { spy } from 'sinon'; import { @@ -11,10 +11,10 @@ import { getGridStringQuickFilterFn, } from '@mui/x-data-grid'; import { getColumnValues, sleep } from 'test/utils/helperFn'; -import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; +import { isJSDOM } from 'test/utils/skipIf'; describe(' - Quick filter', () => { - const { render, clock } = createRenderer(); + const { render } = createRenderer(); const baselineProps = { autoHeight: isJSDOM, @@ -59,24 +59,21 @@ describe(' - Quick filter', () => { } describe('component', () => { - clock.withFakeTimers(); - - it('should apply filter', () => { - render(); + it('should apply filter', async () => { + const { user } = render(); expect(getColumnValues(0)).to.deep.equal(['Nike', 'Adidas', 'Puma']); - fireEvent.change(screen.getByRole('searchbox'), { - target: { value: 'a' }, - }); - clock.runToLast(); + await user.type(screen.getByRole('searchbox'), 'a'); - expect(getColumnValues(0)).to.deep.equal(['Adidas', 'Puma']); + await waitFor(() => { + expect(getColumnValues(0)).to.deep.equal(['Adidas', 'Puma']); + }); }); - it('should allow to customize input splitting', () => { + it('should allow to customize input splitting', async () => { const onFilterModelChange = spy(); - render( + const { user } = render( - Quick filter', () => { expect(onFilterModelChange.callCount).to.equal(0); - fireEvent.change(screen.getByRole('searchbox'), { - target: { value: 'adid, nik' }, - }); - clock.runToLast(); - expect(onFilterModelChange.lastCall.firstArg).to.deep.equal({ - items: [], - logicOperator: 'and', - quickFilterValues: ['adid', 'nik'], - quickFilterLogicOperator: 'and', + await user.click(screen.getByRole('button', { name: 'Search' })); + await user.type(screen.getByRole('searchbox'), 'adid, nik'); + + await waitFor(() => { + expect(onFilterModelChange.lastCall.firstArg).to.deep.equal({ + items: [], + logicOperator: 'and', + quickFilterValues: ['adid', 'nik'], + quickFilterLogicOperator: 'and', + }); }); }); - it('should no prettify user input', () => { - render(); + it('should no prettify user input', async () => { + const { user } = render(); - fireEvent.change(screen.getByRole('searchbox'), { - target: { value: 'adidas nike' }, - }); - clock.runToLast(); + await user.click(screen.getByRole('button', { name: 'Search' })); + await user.type(screen.getByRole('searchbox'), 'adidas nike'); expect(screen.getByRole('searchbox').value).to.equal('adidas nike'); }); @@ -165,11 +161,9 @@ describe(' - Quick filter', () => { expect(screen.getByRole('searchbox').value).to.equal(''); expect(screen.getByRole('searchbox').tabIndex).to.equal(-1); - expect( - screen - .getByRole('button', { name: 'Search' }) - .getAttribute('aria-expanded'), - ).to.equal('false'); + expect(screen.getByRole('button', { name: 'Search' }).getAttribute('aria-expanded')).to.equal( + 'false', + ); }); it('should be expanded by default if there is a value', () => { @@ -177,154 +171,108 @@ describe(' - Quick filter', () => { expect(screen.getByRole('searchbox').value).to.equal('adidas'); expect(screen.getByRole('searchbox').tabIndex).to.equal(0); - expect( - screen - .getByRole('button', { name: 'Search' }) - .getAttribute('aria-expanded'), - ).to.equal('true'); + expect(screen.getByRole('button', { name: 'Search' }).getAttribute('aria-expanded')).to.equal( + 'true', + ); }); - it('should expand when the trigger is clicked', () => { - render(); + it('should expand when the trigger is clicked', async () => { + const { user } = render(); - fireEvent.click(screen.getByRole('button', { name: 'Search' })); + await user.click(screen.getByRole('button', { name: 'Search' })); - expect( - screen - .getByRole('button', { name: 'Search' }) - .getAttribute('aria-expanded'), - ).to.equal('true'); + expect(screen.getByRole('button', { name: 'Search' }).getAttribute('aria-expanded')).to.equal( + 'true', + ); }); - it('should expand when the input changes value', () => { - render(); - - fireEvent.focus(screen.getByRole('searchbox')); + it('should expand when the input changes value', async () => { + const { user } = render(); - fireEvent.change(screen.getByRole('searchbox'), { - target: { value: 'adidas' }, - }); + await user.type(screen.getByRole('searchbox'), 'adidas'); - expect( - screen - .getByRole('button', { name: 'Search' }) - .getAttribute('aria-expanded'), - ).to.equal('true'); + expect(screen.getByRole('button', { name: 'Search' }).getAttribute('aria-expanded')).to.equal( + 'true', + ); }); - it('should collapse when the input is blurred with no value', () => { - render(); + it('should collapse when the escape key is pressed with no value', async () => { + const { user } = render(); - fireEvent.click(screen.getByRole('button', { name: 'Search' })); + await user.click(screen.getByRole('button', { name: 'Search' })); - expect( - screen - .getByRole('button', { name: 'Search' }) - .getAttribute('aria-expanded'), - ).to.equal('true'); + expect(screen.getByRole('button', { name: 'Search' }).getAttribute('aria-expanded')).to.equal( + 'true', + ); - fireEvent.blur(screen.getByRole('searchbox')); + await user.keyboard('[Escape]'); - expect( - screen - .getByRole('button', { name: 'Search' }) - .getAttribute('aria-expanded'), - ).to.equal('false'); + expect(screen.getByRole('button', { name: 'Search' }).getAttribute('aria-expanded')).to.equal( + 'false', + ); }); - it('should collapse when the escape key is pressed with no value', () => { - render(); + it('should clear the input when the escape key is pressed with a value and not collapse the input', async () => { + const { user } = render(); - fireEvent.click(screen.getByRole('button', { name: 'Search' })); + await user.click(screen.getByRole('button', { name: 'Search' })); - // Wait for the input to be focused - clock.runToLast(); + await user.type(screen.getByRole('searchbox'), 'adidas'); - fireEvent.keyDown(screen.getByRole('searchbox'), { - key: 'Escape', - }); - - expect( - screen - .getByRole('button', { name: 'Search' }) - .getAttribute('aria-expanded'), - ).to.equal('false'); - }); - - it('should clear the input when the escape key is pressed with a value and not collapse the input', () => { - render(); - - fireEvent.click(screen.getByRole('button', { name: 'Search' })); - clock.runToLast(); - - fireEvent.change(screen.getByRole('searchbox'), { - target: { value: 'adidas' }, - }); - clock.runToLast(); - - fireEvent.keyDown(screen.getByRole('searchbox'), { - key: 'Escape', - }); + await user.keyboard('[Escape]'); expect(screen.getByRole('searchbox').value).to.equal(''); - expect( - screen - .getByRole('button', { name: 'Search' }) - .getAttribute('aria-expanded'), - ).to.equal('true'); + expect(screen.getByRole('button', { name: 'Search' }).getAttribute('aria-expanded')).to.equal( + 'true', + ); }); - it('should clear the value when the clear button is clicked and focus to `the input', () => { - render(); + it('should clear the value when the clear button is clicked and focus to `the input', async () => { + const { user } = render( + , + ); - fireEvent.click(screen.getByRole('button', { name: 'Clear' })); - clock.runToLast(); + await user.click(screen.getByRole('button', { name: 'Clear' })); expect(screen.getByRole('searchbox').value).to.equal(''); expect(screen.getByRole('searchbox')).toHaveFocus(); }); - it('should focus the input when the trigger is clicked and return focus to the trigger when collapsed', () => { - render(); - - fireEvent.click(screen.getByRole('button', { name: 'Search' })); + it('should focus the input when the trigger is clicked and return focus to the trigger when collapsed', async () => { + const { user } = render(); - // Wait for the input to be focused - clock.runToLast(); - - expect(screen.getByRole('searchbox')).toHaveFocus(); + await user.click(screen.getByRole('button', { name: 'Search' })); - fireEvent.blur(screen.getByRole('searchbox')); + await waitFor(() => { + expect(screen.getByRole('searchbox')).toHaveFocus(); + }); - // Wait for the trigger to be focused - clock.runToLast(); + await user.keyboard('[Escape]'); - expect(screen.getByRole('button', { name: 'Search' })).toHaveFocus(); + expect(screen.getByRole('button', { name: 'Search' })).toHaveFocus(); }); }); describe('quick filter logic', () => { - clock.withFakeTimers(); + it('should return rows that match all values by default', async () => { + const { user } = render(); - it('should return rows that match all values by default', () => { - render(); + await user.click(screen.getByRole('button', { name: 'Search' })); + await user.type(screen.getByRole('searchbox'), 'adid'); - fireEvent.change(screen.getByRole('searchbox'), { - target: { value: 'adid' }, + await waitFor(() => { + expect(getColumnValues(0)).to.deep.equal(['Adidas']); }); - clock.runToLast(); - expect(getColumnValues(0)).to.deep.equal(['Adidas']); + await user.type(screen.getByRole('searchbox'), ' nik'); - fireEvent.change(screen.getByRole('searchbox'), { - target: { value: 'adid nik' }, + await waitFor(() => { + expect(getColumnValues(0)).to.deep.equal([]); }); - clock.runToLast(); - expect(getColumnValues(0)).to.deep.equal([]); }); - it('should return rows that match some values if quickFilterLogicOperator="or"', () => { - render( + it('should return rows that match some values if quickFilterLogicOperator="or"', async () => { + const { user } = render( - Quick filter', () => { />, ); - fireEvent.change(screen.getByRole('searchbox'), { - target: { value: 'adid' }, + await user.click(screen.getByRole('button', { name: 'Search' })); + await user.type(screen.getByRole('searchbox'), 'adid'); + + await waitFor(() => { + expect(getColumnValues(0)).to.deep.equal(['Adidas']); }); - clock.runToLast(); - expect(getColumnValues(0)).to.deep.equal(['Adidas']); - fireEvent.change(screen.getByRole('searchbox'), { - target: { value: 'adid nik' }, + await user.type(screen.getByRole('searchbox'), ' nik'); + + await waitFor(() => { + expect(getColumnValues(0)).to.deep.equal(['Nike', 'Adidas']); }); - clock.runToLast(); - expect(getColumnValues(0)).to.deep.equal(['Nike', 'Adidas']); }); - it('should ignore hidden columns by default', () => { - render( + it('should ignore hidden columns by default', async () => { + const { user } = render( - Quick filter', () => { />, ); - fireEvent.change(screen.getByRole('searchbox'), { target: { value: '1' } }); - clock.runToLast(); - expect(getColumnValues(0)).to.deep.equal([]); + await user.type(screen.getByRole('searchbox'), '1'); + + await waitFor(() => { + expect(getColumnValues(0)).to.deep.equal([]); + }); + + await user.type(screen.getByRole('searchbox'), '[Backspace]2'); - fireEvent.change(screen.getByRole('searchbox'), { target: { value: '2' } }); - clock.runToLast(); expect(getColumnValues(0)).to.deep.equal([]); }); - it('should search hidden columns when quickFilterExcludeHiddenColumns=false', () => { - render( + it('should search hidden columns when quickFilterExcludeHiddenColumns=false', async () => { + const { user } = render( - Quick filter', () => { />, ); - fireEvent.change(screen.getByRole('searchbox'), { target: { value: '1' } }); - clock.runToLast(); - expect(getColumnValues(0)).to.deep.equal(['Adidas']); + await user.type(screen.getByRole('searchbox'), '1'); + + await waitFor(() => { + expect(getColumnValues(0)).to.deep.equal(['Adidas']); + }); + + await user.type(screen.getByRole('searchbox'), '[Backspace]2'); - fireEvent.change(screen.getByRole('searchbox'), { target: { value: '2' } }); - clock.runToLast(); - expect(getColumnValues(0)).to.deep.equal(['Puma']); + await waitFor(() => { + expect(getColumnValues(0)).to.deep.equal(['Puma']); + }); }); - it('should ignore hidden columns when quickFilterExcludeHiddenColumns=true', () => { - render( + it('should ignore hidden columns when quickFilterExcludeHiddenColumns=true', async () => { + const { user } = render( - Quick filter', () => { />, ); - fireEvent.change(screen.getByRole('searchbox'), { target: { value: '1' } }); - clock.runToLast(); - expect(getColumnValues(0)).to.deep.equal([]); + await user.type(screen.getByRole('searchbox'), '1'); + await waitFor(() => { + expect(getColumnValues(0)).to.deep.equal([]); + }); - fireEvent.change(screen.getByRole('searchbox'), { target: { value: '2' } }); - clock.runToLast(); + await user.type(screen.getByRole('searchbox'), '[Backspace]2'); expect(getColumnValues(0)).to.deep.equal([]); }); @@ -427,7 +382,6 @@ describe(' - Quick filter', () => { quickFilterExcludeHiddenColumns: true, }, }); - clock.runToLast(); expect(getColumnValues(0)).to.deep.equal([]); }); @@ -461,12 +415,10 @@ describe(' - Quick filter', () => { expect(getApplyQuickFilterFnSpy.callCount).to.equal(initialCallCount); setProps({ columnVisibilityModel: { brand: false } }); - clock.runToLast(); expect(getColumnValues(0)).to.deep.equal([]); expect(getApplyQuickFilterFnSpy.callCount).to.equal(initialCallCount + 1); setProps({ columnVisibilityModel: { brand: true } }); - clock.runToLast(); expect(getColumnValues(0)).to.deep.equal(['1']); expect(getApplyQuickFilterFnSpy.callCount).to.equal(initialCallCount + 2); }); @@ -494,12 +446,10 @@ describe(' - Quick filter', () => { expect(getApplyQuickFilterFnSpy.callCount).to.equal(0); setProps({ columnVisibilityModel: { brand: false } }); - clock.runToLast(); expect(getColumnValues(0)).to.deep.equal(['0', '1', '2']); expect(getApplyQuickFilterFnSpy.callCount).to.equal(0); setProps({ columnVisibilityModel: { brand: true } }); - clock.runToLast(); expect(getColumnValues(0)).to.deep.equal(['0', '1', '2']); expect(getApplyQuickFilterFnSpy.callCount).to.equal(0); }); @@ -531,20 +481,16 @@ describe(' - Quick filter', () => { expect(getApplyQuickFilterFnSpy.callCount).to.equal(initialCallCount); setProps({ columnVisibilityModel: { brand: false } }); - clock.runToLast(); expect(getColumnValues(0)).to.deep.equal(['1']); expect(getApplyQuickFilterFnSpy.callCount).to.equal(initialCallCount); setProps({ columnVisibilityModel: { brand: true } }); - clock.runToLast(); expect(getColumnValues(0)).to.deep.equal(['1']); expect(getApplyQuickFilterFnSpy.callCount).to.equal(initialCallCount); }); }); describe('column type: string', () => { - clock.withFakeTimers(); - const getRows = ({ quickFilterValues }: Pick) => { const { unmount } = render( - Quick filter', () => { }); describe('column type: number', () => { - clock.withFakeTimers(); - const getRows = ({ quickFilterValues }: Pick) => { const { unmount } = render( - Quick filter', () => { }); describe('column type: singleSelect', () => { - clock.withFakeTimers(); - const getRows = ({ quickFilterValues }: Pick) => { const { unmount } = render( - Quick filter', () => { }); // https://github.com/mui/mui-x/issues/6783 - testSkipIf(isJSDOM)('should not override user input when typing', async () => { + it('should not override user input when typing', async () => { // Warning: this test doesn't fail consistently as it is timing-sensitive. const debounceMs = 50; - render( + const { user } = render( - Quick filter', () => { expect(searchBox.value).to.equal(''); - fireEvent.change(searchBox, { target: { value: 'a' } }); - await sleep(debounceMs - 2); + await user.type(searchBox, `a`); + await act(() => sleep(debounceMs - 2)); expect(searchBox.value).to.equal('a'); - fireEvent.change(searchBox, { target: { value: 'ab' } }); - await sleep(10); + await user.type(searchBox, `b`); + await act(() => sleep(10)); expect(searchBox.value).to.equal('ab'); - fireEvent.change(searchBox, { target: { value: 'abc' } }); - await sleep(debounceMs * 2); + await user.type(searchBox, `c`); + await act(() => sleep(debounceMs * 2)); expect(searchBox.value).to.equal('abc'); }); diff --git a/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx b/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx index daaa960b898b5..e51349df762c5 100644 --- a/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; import { RefObject } from '@mui/x-internals/types'; -import { createRenderer, screen, act, waitFor, fireEvent } from '@mui/internal-test-utils'; +import { createRenderer, screen, act, waitFor } from '@mui/internal-test-utils'; import { DataGrid, DataGridProps, @@ -11,7 +11,6 @@ import { useGridApiRef, GridApi, GridRowSelectionModel, - GridPreferencePanelsValue, } from '@mui/x-data-grid'; import { getCell, @@ -417,26 +416,23 @@ describe(' - Row selection', () => { expect(input2.checked).to.equal(true); }); - testSkipIf(isJSDOM)('should remove the selection from rows that are filtered out', async () => { - render( - , - ); + it('should remove the selection from rows that are filtered out', async () => { + const { user } = render(); const selectAllCheckbox = screen.getByRole('checkbox', { name: 'Select all rows' }); - fireEvent.click(selectAllCheckbox); + await user.click(selectAllCheckbox); expect(getSelectedRowIds()).to.deep.equal([0, 1, 2, 3]); expect(grid('selectedRowCount')?.textContent).to.equal('4 rows selected'); - fireEvent.change(screen.getByRole('spinbutton', { name: 'Value' }), { - target: { value: 1 }, - }); + const idText = screen.getByRole('columnheader', { name: 'id' }); + await user.hover(idText); + const idMenu = idText.querySelector('button[aria-label="id column menu"]')!; + await user.click(idMenu); + + const filterButton = screen.getByText('Filter'); + await user.click(filterButton); + + await user.keyboard('1'); + await waitFor(() => { // Previous selection is cleaned with only the filtered rows expect(getSelectedRowIds()).to.deep.equal([1]); @@ -445,42 +441,38 @@ describe(' - Row selection', () => { }); it('should only select filtered items when "select all" is toggled after applying a filter', async () => { - render( - , - ); + const { user } = render(); const selectAllCheckbox = screen.getByRole('checkbox', { name: 'Select all rows' }); - fireEvent.click(selectAllCheckbox); + await user.click(selectAllCheckbox); await waitFor(() => { expect(getSelectedRowIds()).to.deep.equal([0, 1, 2, 3]); }); expect(grid('selectedRowCount')?.textContent).to.equal('4 rows selected'); - // Click on Menu in id header column - fireEvent.change(screen.getByRole('spinbutton', { name: 'Value' }), { - target: { value: 1 }, - }); + const idText = screen.getByRole('columnheader', { name: 'id' }); + await user.hover(idText); + const idMenu = idText.querySelector('button[aria-label="id column menu"]')!; + await user.click(idMenu); + + const filterButton = screen.getByText('Filter'); + await user.click(filterButton); + + await user.keyboard('1'); + await waitFor(() => { // Previous selection is cleared and only the filtered row is selected expect(getSelectedRowIds()).to.deep.equal([1]); }); expect(grid('selectedRowCount')?.textContent).to.equal('1 row selected'); - fireEvent.click(selectAllCheckbox); // Unselect all + await user.click(selectAllCheckbox); // Unselect all await waitFor(() => { expect(getSelectedRowIds()).to.deep.equal([]); }); expect(grid('selectedRowCount')).to.equal(null); - fireEvent.click(selectAllCheckbox); // Select all filtered rows + await user.click(selectAllCheckbox); // Select all filtered rows await waitFor(() => { expect(getSelectedRowIds()).to.deep.equal([1]); }); diff --git a/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx b/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx index 3b9fa7e7d3c72..c2115475d1a9e 100644 --- a/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx @@ -864,6 +864,11 @@ describe(' - Rows', () => { // In Chrome non-headless and Edge this test is flaky testSkipIf(!isJSDOM || !userAgent.includes('Headless') || /edg/i.test(userAgent))( 'should position correctly the render zone when changing pageSize to a lower value and moving to next page', + { + // Retry the test because it is flaky + retries: 3, + }, + // @ts-expect-error mocha types are incorrect async () => { const data = getBasicGridData(120, 3); const columnHeaderHeight = 50; diff --git a/packages/x-data-grid/src/tests/slots.DataGrid.test.tsx b/packages/x-data-grid/src/tests/slots.DataGrid.test.tsx index 13c120e71f5b5..c44573bca9149 100644 --- a/packages/x-data-grid/src/tests/slots.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/slots.DataGrid.test.tsx @@ -167,7 +167,7 @@ describe(' - Slots', () => { 'MUI X: useGridRootProps should only be used inside the DataGrid, DataGridPro or DataGridPremium component.', reactMajor < 19 && 'MUI X: useGridRootProps should only be used inside the DataGrid, DataGridPro or DataGridPremium component.', - reactMajor < 19 && 'The above error occurred in the component', + reactMajor < 19 && 'The above error occurred in the component', ]); }); diff --git a/packages/x-data-grid/vitest.config.browser.mts b/packages/x-data-grid/vitest.config.browser.mts new file mode 100644 index 0000000000000..8362de2857660 --- /dev/null +++ b/packages/x-data-grid/vitest.config.browser.mts @@ -0,0 +1,23 @@ +/// +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'browser', + browser: { + enabled: true, + instances: [ + { + browser: 'chromium', + launch: { + // Required for tests which use scrollbars. + ignoreDefaultArgs: ['--hide-scrollbars'], + }, + }, + ], + }, + }, +}); diff --git a/packages/x-data-grid/vitest.config.jsdom.mts b/packages/x-data-grid/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..50dd48e254699 --- /dev/null +++ b/packages/x-data-grid/vitest.config.jsdom.mts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + }, +}); diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.test.tsx index 251be3a43bde3..10b2048c56286 100644 --- a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.test.tsx @@ -20,15 +20,9 @@ describe('', () => { Component: DateRangePicker, }); - const originalMatchMedia = window.matchMedia; - - afterEach(() => { - window.matchMedia = originalMatchMedia; - }); - it('should not use the mobile picker by default', () => { + stubMatchMedia(true); // Test with accessible DOM structure - window.matchMedia = stubMatchMedia(true); const { unmount } = renderWithProps({ enableAccessibleFieldDOMStructure: true }); openPicker({ type: 'date-range', initialFocus: 'start', fieldType: 'single-input' }); expect(screen.queryByRole('dialog')).to.have.class(pickerPopperClasses.root); @@ -36,15 +30,14 @@ describe('', () => { unmount(); // Test with non-accessible DOM structure - window.matchMedia = stubMatchMedia(true); renderWithProps({ enableAccessibleFieldDOMStructure: false }); openPicker({ type: 'date-range', initialFocus: 'start', fieldType: 'single-input' }); expect(screen.queryByRole('dialog')).to.have.class(pickerPopperClasses.root); }); it('should use the mobile picker when `useMediaQuery` returns `false`', () => { + stubMatchMedia(false); // Test with accessible DOM structure - window.matchMedia = stubMatchMedia(false); const { unmount } = renderWithProps({ enableAccessibleFieldDOMStructure: true }); openPicker({ type: 'date-range', initialFocus: 'start', fieldType: 'single-input' }); expect(screen.queryByRole('dialog')).not.to.have.class(pickerPopperClasses.root); @@ -52,7 +45,6 @@ describe('', () => { unmount(); // Test with non-accessible DOM structure - window.matchMedia = stubMatchMedia(false); renderWithProps({ enableAccessibleFieldDOMStructure: false }); openPicker({ type: 'date-range', initialFocus: 'start', fieldType: 'single-input' }); expect(screen.queryByRole('dialog')).not.to.have.class(pickerPopperClasses.root); diff --git a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx index 6fd9927fdaa11..b3032d3acc2fc 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { spy, useFakeTimers, SinonFakeTimers } from 'sinon'; +import { spy } from 'sinon'; import { fireEvent, screen, act, within, waitFor } from '@mui/internal-test-utils'; import { createTheme, ThemeProvider } from '@mui/material/styles'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; @@ -17,6 +17,7 @@ import { getTextbox, } from 'test/utils/pickers'; import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; +import { vi } from 'vitest'; const getPickerDay = (name: string, picker = 'January 2018') => within(screen.getByRole('grid', { name: picker })).getByRole('gridcell', { name }); @@ -696,15 +697,12 @@ describe('', () => { }); describe('disabled dates', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; - beforeEach(() => { - timer = useFakeTimers({ now: new Date(2018, 0, 10), toFake: ['Date'] }); + vi.setSystemTime(new Date(2018, 0, 10)); }); afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); it('should respect the disablePast prop', async () => { diff --git a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx index c5944e250add1..56eb20140dfe3 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx @@ -8,21 +8,18 @@ import { getFieldSectionsContainer, expectFieldValueV7, } from 'test/utils/pickers'; -import { SinonFakeTimers, useFakeTimers } from 'sinon'; +import { vi } from 'vitest'; import { DesktopDateTimeRangePicker } from '../DesktopDateTimeRangePicker'; describe('', () => { const { render } = createPickerRenderer(); - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; - beforeEach(() => { - timer = useFakeTimers({ now: new Date(2018, 0, 10, 10, 16, 0), toFake: ['Date'] }); + vi.setSystemTime(new Date(2018, 0, 10, 10, 16, 0)); }); afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); describe('value selection', () => { diff --git a/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/MobileDateTimeRangePicker.test.tsx b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/MobileDateTimeRangePicker.test.tsx index 77943ca072f21..d04d45af40a20 100644 --- a/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/MobileDateTimeRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/MobileDateTimeRangePicker.test.tsx @@ -8,21 +8,18 @@ import { expectFieldValueV7, } from 'test/utils/pickers'; import { MobileDateTimeRangePicker } from '@mui/x-date-pickers-pro/MobileDateTimeRangePicker'; -import { SinonFakeTimers, useFakeTimers } from 'sinon'; +import { vi } from 'vitest'; describe('', () => { const { render } = createPickerRenderer(); describe('value selection', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; - beforeEach(() => { - timer = useFakeTimers({ now: new Date(2018, 0, 10, 10, 16, 0), toFake: ['Date'] }); + vi.setSystemTime(new Date(2018, 0, 10, 10, 16, 0)); }); afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); it('should cycle focused views among the visible step after selection', async () => { diff --git a/packages/x-date-pickers-pro/tsconfig.json b/packages/x-date-pickers-pro/tsconfig.json index 82f2e7898632c..048fc82f52d59 100644 --- a/packages/x-date-pickers-pro/tsconfig.json +++ b/packages/x-date-pickers-pro/tsconfig.json @@ -10,7 +10,8 @@ "mocha", "node" ], - "noImplicitAny": false + "noImplicitAny": false, + "skipLibCheck": true }, "include": ["src/**/*", "../../test/utils/addChaiAssertions.ts"] } diff --git a/packages/x-date-pickers-pro/vitest.config.browser.mts b/packages/x-date-pickers-pro/vitest.config.browser.mts new file mode 100644 index 0000000000000..1b6f538191a3a --- /dev/null +++ b/packages/x-date-pickers-pro/vitest.config.browser.mts @@ -0,0 +1,19 @@ +/// +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'browser', + browser: { + enabled: true, + instances: [ + { + browser: 'chromium', + }, + ], + }, + }, +}); diff --git a/packages/x-date-pickers-pro/vitest.config.jsdom.mts b/packages/x-date-pickers-pro/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..50dd48e254699 --- /dev/null +++ b/packages/x-date-pickers-pro/vitest.config.jsdom.mts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + }, +}); diff --git a/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts b/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts index 6004c385f7bac..758a8fd66b53f 100644 --- a/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts +++ b/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts @@ -82,7 +82,7 @@ export class AdapterDateFns implements MuiPickersAdapter { constructor({ locale, formats }: AdapterOptions = {}) { - /* istanbul ignore next */ + /* v8 ignore start */ if (process.env.NODE_ENV !== 'production') { if (typeof addDays !== 'function') { throw new Error( @@ -98,6 +98,7 @@ export class AdapterDateFns ); } } + /* v8 ignore stop */ super({ locale: locale ?? enUS, formats, longFormatters }); } diff --git a/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts b/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts index 14375117a0131..60e794d08e1d5 100644 --- a/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts +++ b/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts @@ -121,7 +121,7 @@ export class AdapterDateFnsJalali implements MuiPickersAdapter { constructor({ locale, formats }: AdapterOptions = {}) { - /* istanbul ignore next */ + /* v8 ignore start */ if (process.env.NODE_ENV !== 'production') { if (typeof addDays !== 'function') { throw new Error( @@ -137,6 +137,7 @@ export class AdapterDateFnsJalali ); } } + /* v8 ignore stop */ super({ locale: locale ?? defaultLocale, // some formats are different in jalali adapter, diff --git a/packages/x-date-pickers/src/AdapterDateFnsJalaliV2/AdapterDateFnsJalaliV2.ts b/packages/x-date-pickers/src/AdapterDateFnsJalaliV2/AdapterDateFnsJalaliV2.ts index 1fdad7a49c363..6d21b8caf2c3d 100644 --- a/packages/x-date-pickers/src/AdapterDateFnsJalaliV2/AdapterDateFnsJalaliV2.ts +++ b/packages/x-date-pickers/src/AdapterDateFnsJalaliV2/AdapterDateFnsJalaliV2.ts @@ -1,6 +1,7 @@ // date-fns-jalali@<3 has no exports field defined // See https://github.com/date-fns/date-fns/issues/1781 /* eslint-disable import/extensions, class-methods-use-this */ +/* v8 ignore start */ // @ts-nocheck import addSeconds from 'date-fns-jalali/addSeconds/index.js'; import addMinutes from 'date-fns-jalali/addMinutes/index.js'; @@ -47,6 +48,7 @@ import isWithinInterval from 'date-fns-jalali/isWithinInterval/index.js'; import defaultLocale from 'date-fns-jalali/locale/fa-IR/index.js'; import type { Locale as DateFnsLocale } from 'date-fns-jalali'; import longFormatters from 'date-fns-jalali/_lib/format/longFormatters/index.js'; +/* v8 ignore end */ import { AdapterFormats, AdapterOptions, MuiPickersAdapter } from '../models'; import { AdapterDateFnsBase } from '../AdapterDateFnsBase'; @@ -125,7 +127,7 @@ export class AdapterDateFnsJalali implements MuiPickersAdapter { constructor({ locale, formats }: AdapterOptions = {}) { - /* istanbul ignore next */ + /* v8 ignore start */ if (process.env.NODE_ENV !== 'production') { if (typeof addDays !== 'function') { throw new Error( @@ -136,6 +138,7 @@ export class AdapterDateFnsJalali ); } } + /* v8 ignore stop */ super({ locale: locale ?? defaultLocale, // some formats are different in jalali adapter, diff --git a/packages/x-date-pickers/src/AdapterDateFnsV2/AdapterDateFnsV2.ts b/packages/x-date-pickers/src/AdapterDateFnsV2/AdapterDateFnsV2.ts index 1c0c2b2947f7c..0059fd9a1f49c 100644 --- a/packages/x-date-pickers/src/AdapterDateFnsV2/AdapterDateFnsV2.ts +++ b/packages/x-date-pickers/src/AdapterDateFnsV2/AdapterDateFnsV2.ts @@ -1,6 +1,7 @@ // date-fns@<3 has no exports field defined // See https://github.com/date-fns/date-fns/issues/1781 /* eslint-disable import/extensions, class-methods-use-this */ +/* v8 ignore start */ // @ts-nocheck import addDays from 'date-fns/addDays/index.js'; import addSeconds from 'date-fns/addSeconds/index.js'; @@ -47,6 +48,7 @@ import isWithinInterval from 'date-fns/isWithinInterval/index.js'; import defaultLocale from 'date-fns/locale/en-US/index.js'; import type { Locale as DateFnsLocale } from 'date-fns'; import longFormatters from 'date-fns/_lib/format/longFormatters/index.js'; +/* v8 ignore end */ import { AdapterFormats, AdapterOptions, MuiPickersAdapter } from '../models'; import { AdapterDateFnsBase } from '../AdapterDateFnsBase'; @@ -86,7 +88,7 @@ export class AdapterDateFns implements MuiPickersAdapter { constructor({ locale, formats }: AdapterOptions = {}) { - /* istanbul ignore next */ + /* v8 ignore start */ if (process.env.NODE_ENV !== 'production') { if (typeof addDays !== 'function') { throw new Error( @@ -97,6 +99,7 @@ export class AdapterDateFns ); } } + /* v8 ignore stop */ super({ locale: locale ?? defaultLocale, formats, longFormatters }); } diff --git a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts index cc26fbdcac6a2..e5162683884dd 100644 --- a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts +++ b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts @@ -1,4 +1,5 @@ /* eslint-disable class-methods-use-this */ +/* v8 ignore start */ import defaultDayjs, { Dayjs } from 'dayjs'; // dayjs has no exports field defined // See https://github.com/iamkun/dayjs/issues/2562 @@ -8,6 +9,7 @@ import customParseFormatPlugin from 'dayjs/plugin/customParseFormat.js'; import localizedFormatPlugin from 'dayjs/plugin/localizedFormat.js'; import isBetweenPlugin from 'dayjs/plugin/isBetween.js'; import advancedFormatPlugin from 'dayjs/plugin/advancedFormat.js'; +/* v8 ignore stop */ /* eslint-enable import/extensions */ import { warnOnce } from '@mui/x-internals/warning'; import { @@ -208,7 +210,7 @@ export class AdapterDayjs implements MuiPickersAdapter { const timezone = defaultDayjs.tz.guess(); // We can't change the system timezone in the tests - /* istanbul ignore next */ + /* v8 ignore next 3 */ if (timezone !== 'UTC') { return defaultDayjs.tz(value, timezone); } @@ -220,7 +222,7 @@ export class AdapterDayjs implements MuiPickersAdapter { }; private createUTCDate = (value: string | undefined): Dayjs => { - /* istanbul ignore next */ + /* v8 ignore next 3 */ if (!this.hasUTCPlugin()) { throw new Error(MISSING_UTC_PLUGIN); } @@ -229,12 +231,12 @@ export class AdapterDayjs implements MuiPickersAdapter { }; private createTZDate = (value: string | undefined, timezone: PickersTimezone): Dayjs => { - /* istanbul ignore next */ + /* v8 ignore next 3 */ if (!this.hasUTCPlugin()) { throw new Error(MISSING_UTC_PLUGIN); } - /* istanbul ignore next */ + /* v8 ignore next 3 */ if (!this.hasTimezonePlugin()) { throw new Error(MISSING_TIMEZONE_PLUGIN); } @@ -250,7 +252,7 @@ export class AdapterDayjs implements MuiPickersAdapter { let localeObject = locales[locale]; if (localeObject === undefined) { - /* istanbul ignore next */ + /* v8 ignore start */ if (process.env.NODE_ENV !== 'production') { warnOnce([ 'MUI X: Your locale has not been found.', @@ -259,6 +261,7 @@ export class AdapterDayjs implements MuiPickersAdapter { 'fallback on English locale.', ]); } + /* v8 ignore stop */ localeObject = locales.en; } @@ -280,7 +283,7 @@ export class AdapterDayjs implements MuiPickersAdapter { if (timezone !== 'UTC') { const fixedValue = value.tz(this.cleanTimezone(timezone), true); // TODO: Simplify the case when we raise the `dayjs` peer dep to 1.11.12 (https://github.com/iamkun/dayjs/releases/tag/v1.11.12) - /* istanbul ignore next */ + /* v8 ignore next 3 */ // @ts-ignore if (fixedValue.$offset === (value.$offset ?? 0)) { return value; @@ -345,7 +348,7 @@ export class AdapterDayjs implements MuiPickersAdapter { } if (timezone === 'UTC') { - /* istanbul ignore next */ + /* v8 ignore next 3 */ if (!this.hasUTCPlugin()) { throw new Error(MISSING_UTC_PLUGIN); } @@ -365,7 +368,7 @@ export class AdapterDayjs implements MuiPickersAdapter { return value; } - /* istanbul ignore next */ + /* v8 ignore next */ throw new Error(MISSING_TIMEZONE_PLUGIN); } @@ -389,7 +392,7 @@ export class AdapterDayjs implements MuiPickersAdapter { }; public is12HourCycleInCurrentLocale = () => { - /* istanbul ignore next */ + /* v8 ignore next */ return /A|a/.test(this.getLocaleFormats().LT || ''); }; diff --git a/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts b/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts index faf174a2b1099..113e89a2a496a 100644 --- a/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts +++ b/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts @@ -197,7 +197,7 @@ export class AdapterLuxon implements MuiPickersAdapter { return this.locale; }; - /* istanbul ignore next */ + /* v8 ignore start */ public is12HourCycleInCurrentLocale = () => { if (typeof Intl === 'undefined' || typeof Intl.DateTimeFormat === 'undefined') { return true; // Luxon defaults to en-US if Intl not found @@ -207,6 +207,7 @@ export class AdapterLuxon implements MuiPickersAdapter { new Intl.DateTimeFormat(this.locale, { hour: 'numeric' })?.resolvedOptions()?.hour12, ); }; + /* v8 ignore stop */ public expandFormat = (format: string) => { // Extract escaped section to avoid extending them @@ -482,7 +483,7 @@ export class AdapterLuxon implements MuiPickersAdapter { }; public getWeekNumber = (value: DateTime) => { - /* istanbul ignore next */ + /* v8 ignore next */ return value.localWeekNumber ?? value.weekNumber; }; diff --git a/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts b/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts index 2f5a9020cb078..a07b58c2024b7 100644 --- a/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts +++ b/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts @@ -174,7 +174,7 @@ export class AdapterMoment implements MuiPickersAdapter { }; private createTZDate = (value: string | undefined, timezone: PickersTimezone): Moment => { - /* istanbul ignore next */ + /* v8 ignore next 3 */ if (!this.hasTimezonePlugin()) { throw new Error(MISSING_TIMEZONE_PLUGIN); } @@ -235,7 +235,7 @@ export class AdapterMoment implements MuiPickersAdapter { } if (!this.hasTimezonePlugin()) { - /* istanbul ignore next */ + /* v8 ignore next 3 */ if (timezone !== 'default') { throw new Error(MISSING_TIMEZONE_PLUGIN); } diff --git a/packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.test.tsx b/packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.test.tsx index 865999bc4294a..023bc85553ecf 100644 --- a/packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.test.tsx +++ b/packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.test.tsx @@ -1,4 +1,4 @@ -import moment, { Moment } from 'moment'; +import moment, { Moment } from 'moment-hijri'; import { expect } from 'chai'; import { DateTimeField } from '@mui/x-date-pickers/DateTimeField'; import { AdapterMomentHijri } from '@mui/x-date-pickers/AdapterMomentHijri'; @@ -9,12 +9,28 @@ import { describeHijriAdapter, buildFieldInteractions, } from 'test/utils/pickers'; -import 'moment/locale/ar'; +import 'moment/locale/ar-sa'; +import { beforeAll } from 'vitest'; +import { isJSDOM } from 'test/utils/skipIf'; describe('', () => { + beforeAll(() => { + if (!isJSDOM) { + // Vitest browser mode does not correctly load the locale + // This is the minimal amount of locale data needed to run the tests + moment.updateLocale('ar-sa', { + weekdays: 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdaysShort: 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'), + meridiem: (hour) => (hour < 12 ? 'ص' : 'م'), + postformat: (input) => + input.replace(/\d/g, (match) => '٠١٢٣٤٥٦٧٨٩'[match]).replace(/,/g, '،'), + }); + } + }); + describeHijriAdapter(AdapterMomentHijri, { before: () => { - moment.locale('ar-SA'); + moment.locale('ar-sa'); }, after: () => { moment.locale('en'); diff --git a/packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.ts b/packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.ts index 9d640b095bb61..e200a847444fb 100644 --- a/packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.ts +++ b/packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.ts @@ -1,4 +1,5 @@ /* eslint-disable class-methods-use-this */ +/* v8 ignore next */ import defaultHMoment, { Moment } from 'moment-hijri'; import { AdapterMoment } from '../AdapterMoment'; import { @@ -144,10 +145,12 @@ export class AdapterMomentHijri extends AdapterMoment implements MuiPickersAdapt return this.moment(value).locale('ar-SA') as unknown as R; }; + /* v8 ignore next 3 */ public getTimezone = (): string => { return 'default'; }; + /* v8 ignore next 3 */ public setTimezone = (value: Moment): Moment => { return value; }; diff --git a/packages/x-date-pickers/src/AdapterMomentJalaali/AdapterMomentJalaali.ts b/packages/x-date-pickers/src/AdapterMomentJalaali/AdapterMomentJalaali.ts index 387cf89b5074f..4091419e588b4 100644 --- a/packages/x-date-pickers/src/AdapterMomentJalaali/AdapterMomentJalaali.ts +++ b/packages/x-date-pickers/src/AdapterMomentJalaali/AdapterMomentJalaali.ts @@ -1,4 +1,5 @@ /* eslint-disable class-methods-use-this */ +/* v8 ignore next */ import defaultJMoment, { Moment } from 'moment-jalaali'; import { AdapterMoment } from '../AdapterMoment'; import { diff --git a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx index 03cf55d43071f..fc07d0e5927e5 100644 --- a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx +++ b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx @@ -5,7 +5,8 @@ import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; import { PickersDay } from '@mui/x-date-pickers/PickersDay'; import { createPickerRenderer, adapterToUse } from 'test/utils/pickers'; import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; -import { SinonFakeTimers, useFakeTimers, spy } from 'sinon'; +import { spy } from 'sinon'; +import { vi } from 'vitest'; describe('', () => { const { render } = createPickerRenderer(); @@ -127,15 +128,12 @@ describe('', () => { }); describe('with fake timers', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; - beforeEach(() => { - timer = useFakeTimers({ now: new Date(2019, 0, 2), toFake: ['Date'] }); + vi.setSystemTime(new Date(2019, 0, 2)); }); afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); // test: https://github.com/mui/mui-x/issues/12373 diff --git a/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx b/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx index ca9fa61f0d8bc..ec641adf481a5 100644 --- a/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx +++ b/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx @@ -9,15 +9,12 @@ describe('', () => { const { render } = createPickerRenderer(); it('should render in mobile mode when `useMediaQuery` returns `false`', async () => { - const originalMatchMedia = window.matchMedia; - window.matchMedia = stubMatchMedia(false); + stubMatchMedia(false); const { user } = render(); await user.click(screen.getByLabelText(/Choose date/)); expect(screen.queryByRole('dialog')).to.not.equal(null); - - window.matchMedia = originalMatchMedia; }); describe('form behavior', () => { diff --git a/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx b/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx index adb5239e0e909..7b583b1c268ba 100644 --- a/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx @@ -8,14 +8,11 @@ describe('', () => { const { render } = createPickerRenderer(); it('should render in mobile mode when `useMediaQuery` returns `false`', async () => { - const originalMatchMedia = window.matchMedia; - window.matchMedia = stubMatchMedia(false); + stubMatchMedia(false); const { user } = render(); await user.click(screen.getByLabelText(/Choose date/)); expect(screen.queryByRole('dialog')).to.not.equal(null); - - window.matchMedia = originalMatchMedia; }); }); diff --git a/packages/x-date-pickers/src/MonthCalendar/tests/MonthCalendar.test.tsx b/packages/x-date-pickers/src/MonthCalendar/tests/MonthCalendar.test.tsx index b36fd310860e0..6155f644dea3e 100644 --- a/packages/x-date-pickers/src/MonthCalendar/tests/MonthCalendar.test.tsx +++ b/packages/x-date-pickers/src/MonthCalendar/tests/MonthCalendar.test.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; -import { spy, useFakeTimers, SinonFakeTimers } from 'sinon'; +import { spy } from 'sinon'; import { expect } from 'chai'; import { fireEvent, screen } from '@mui/internal-test-utils'; import { MonthCalendar } from '@mui/x-date-pickers/MonthCalendar'; import { createPickerRenderer, adapterToUse } from 'test/utils/pickers'; +import { vi } from 'vitest'; describe('', () => { const { render } = createPickerRenderer(); @@ -27,15 +28,12 @@ describe('', () => { }); describe('with fake timers', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; - beforeEach(() => { - timer = useFakeTimers({ now: new Date(2019, 0, 1), toFake: ['Date'] }); + vi.setSystemTime(new Date(2019, 0, 1)); }); afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); it('should select start of month without time when no initial value is present', () => { @@ -161,15 +159,12 @@ describe('', () => { }); describe('with fake timers', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; - beforeEach(() => { - timer = useFakeTimers({ now: new Date(2019, 0, 1), toFake: ['Date'] }); + vi.setSystemTime(new Date(2019, 0, 1)); }); afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); it('should disable months after initial render when "disableFuture" prop changes', async () => { diff --git a/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx b/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx index db5f770ed0b2e..46a0c2bd969b9 100644 --- a/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx +++ b/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx @@ -8,14 +8,11 @@ describe('', () => { const { render } = createPickerRenderer(); it('should render in mobile mode when `useMediaQuery` returns `false`', async () => { - const originalMatchMedia = window.matchMedia; - window.matchMedia = stubMatchMedia(false); + stubMatchMedia(false); const { user } = render(); await user.click(screen.getByLabelText(/Choose time/)); expect(screen.queryByRole('dialog')).to.not.equal(null); - - window.matchMedia = originalMatchMedia; }); }); diff --git a/packages/x-date-pickers/src/YearCalendar/tests/YearCalendar.test.tsx b/packages/x-date-pickers/src/YearCalendar/tests/YearCalendar.test.tsx index dc65ccda80150..841ac200afc5f 100644 --- a/packages/x-date-pickers/src/YearCalendar/tests/YearCalendar.test.tsx +++ b/packages/x-date-pickers/src/YearCalendar/tests/YearCalendar.test.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; -import { spy, useFakeTimers, SinonFakeTimers } from 'sinon'; +import { spy } from 'sinon'; import { expect } from 'chai'; import { act, fireEvent, screen } from '@mui/internal-test-utils'; import { YearCalendar } from '@mui/x-date-pickers/YearCalendar'; import { createPickerRenderer, adapterToUse } from 'test/utils/pickers'; +import { vi } from 'vitest'; describe('', () => { const { render } = createPickerRenderer(); @@ -171,15 +172,12 @@ describe('', () => { }); describe('with fake timers', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; - beforeEach(() => { - timer = useFakeTimers({ now: new Date(2019, 0, 1), toFake: ['Date'] }); + vi.setSystemTime(new Date(2019, 0, 1)); }); afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); it('should disable years after initial render when "disableFuture" prop changes', () => { diff --git a/packages/x-date-pickers/src/internals/utils/date-utils.test.ts b/packages/x-date-pickers/src/internals/utils/date-utils.test.ts index e27f519a28878..56c2a335252cd 100644 --- a/packages/x-date-pickers/src/internals/utils/date-utils.test.ts +++ b/packages/x-date-pickers/src/internals/utils/date-utils.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { adapterToUse } from 'test/utils/pickers'; -import { useFakeTimers, SinonFakeTimers } from 'sinon'; +import { vi } from 'vitest'; import { findClosestEnabledDate } from './date-utils'; describe('findClosestEnabledDate', () => { @@ -101,15 +101,12 @@ describe('findClosestEnabledDate', () => { }); describe('fake clock', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; - beforeEach(() => { - timer = useFakeTimers({ now: new Date('2000-01-02'), toFake: ['Date'] }); + vi.setSystemTime(new Date('2000-01-02')); }); afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); it('should return now with given time part if disablePast and now is valid', () => { @@ -176,15 +173,12 @@ describe('findClosestEnabledDate', () => { }); describe('fake clock hours', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; - beforeEach(() => { - timer = useFakeTimers({ now: new Date('2000-01-02T11:12:13.123Z'), toFake: ['Date'] }); + vi.setSystemTime(new Date('2000-01-02T11:12:13.123Z')); }); afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); it('should keep the time of the `date` when `disablePast`', () => { diff --git a/packages/x-date-pickers/tsconfig.json b/packages/x-date-pickers/tsconfig.json index 5f862d31ec2ba..f63ee3ebfc2ad 100644 --- a/packages/x-date-pickers/tsconfig.json +++ b/packages/x-date-pickers/tsconfig.json @@ -8,7 +8,8 @@ "mocha", "node" ], - "noImplicitAny": false + "noImplicitAny": false, + "skipLibCheck": true }, "include": ["src/**/*", "../../test/utils/addChaiAssertions.ts"] } diff --git a/packages/x-date-pickers/vitest.config.browser.mts b/packages/x-date-pickers/vitest.config.browser.mts new file mode 100644 index 0000000000000..baded152ae863 --- /dev/null +++ b/packages/x-date-pickers/vitest.config.browser.mts @@ -0,0 +1,30 @@ +/// +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; + +import { getTestName } from '../../scripts/getTestName.mts'; +import { filterReplace } from './vitest.config.jsdom.mts'; + +export default mergeConfig(sharedConfig, { + plugins: [filterReplace], + resolve: { + alias: [ + { + find: 'moment/locale', + replacement: 'moment/dist/locale', + }, + ], + }, + test: { + name: getTestName(import.meta.url), + environment: 'browser', + browser: { + enabled: true, + instances: [ + { + browser: 'chromium', + }, + ], + }, + }, +}); diff --git a/packages/x-date-pickers/vitest.config.jsdom.mts b/packages/x-date-pickers/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..44cc271f3ef00 --- /dev/null +++ b/packages/x-date-pickers/vitest.config.jsdom.mts @@ -0,0 +1,28 @@ +import { mergeConfig } from 'vitest/config'; +// eslint-disable-next-line import/no-relative-packages +import { redirectImports } from '../../test/vite-plugin-filter-replace.mts'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export const filterReplace = redirectImports([ + { + test: /\/AdapterDateFnsV2\//, + from: 'date-fns', + to: 'date-fns-v2', + include: ['date-fns-v2/locale', 'date-fns-v2/**/*.js'], + }, + { + test: /\/AdapterDateFnsJalaliV2\//, + from: 'date-fns-jalali', + to: 'date-fns-jalali-v2', + include: ['date-fns-jalali-v2/locale', 'date-fns-jalali-v2/**/*.js'], + }, +]); + +export default mergeConfig(sharedConfig, { + plugins: [filterReplace], + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + }, +}); diff --git a/packages/x-internals/vitest.config.jsdom.mts b/packages/x-internals/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..50dd48e254699 --- /dev/null +++ b/packages/x-internals/vitest.config.jsdom.mts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + }, +}); diff --git a/packages/x-license/vitest.config.browser.mts b/packages/x-license/vitest.config.browser.mts new file mode 100644 index 0000000000000..1b6f538191a3a --- /dev/null +++ b/packages/x-license/vitest.config.browser.mts @@ -0,0 +1,19 @@ +/// +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'browser', + browser: { + enabled: true, + instances: [ + { + browser: 'chromium', + }, + ], + }, + }, +}); diff --git a/packages/x-license/vitest.config.jsdom.mts b/packages/x-license/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..50dd48e254699 --- /dev/null +++ b/packages/x-license/vitest.config.jsdom.mts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + }, +}); diff --git a/packages/x-telemetry/src/runtime/config.test.ts b/packages/x-telemetry/src/runtime/config.test.ts index cd492ff95a2bf..73376ad383c57 100644 --- a/packages/x-telemetry/src/runtime/config.test.ts +++ b/packages/x-telemetry/src/runtime/config.test.ts @@ -1,20 +1,18 @@ /* eslint-disable no-underscore-dangle */ -import sinon from 'sinon'; import { expect } from 'chai'; import { ponyfillGlobal } from '@mui/utils'; +import { vi } from 'vitest'; import { muiXTelemetrySettings } from '@mui/x-telemetry'; import { getTelemetryEnvConfig } from './config'; describe('Telemetry: getTelemetryConfig', () => { beforeEach(() => { - sinon.stub(process, 'env').value({ - NODE_ENV: 'development', - }); + vi.stubEnv('NODE_ENV', 'development'); }); afterEach(() => { - sinon.restore(); + vi.unstubAllEnvs(); // Reset env config cache getTelemetryEnvConfig(true); }); @@ -25,12 +23,12 @@ describe('Telemetry: getTelemetryConfig', () => { function testConfigWithDisabledEnv(envKey: string) { it(`should be disabled, if ${envKey} is set to '1'`, () => { - sinon.stub(process, 'env').value({ [envKey]: '1' }); + vi.stubEnv(envKey, '1'); expect(getTelemetryEnvConfig(true).IS_COLLECTING).equal(false); }); it(`should be enabled, if ${envKey} is set to '0'`, () => { - sinon.stub(process, 'env').value({ [envKey]: '0' }); + vi.stubEnv(envKey, '0'); expect(getTelemetryEnvConfig(true).IS_COLLECTING).equal(true); }); } @@ -41,14 +39,14 @@ describe('Telemetry: getTelemetryConfig', () => { it('should be disabled if global.__MUI_X_TELEMETRY_DISABLED__ is set to `1`', () => { ponyfillGlobal.__MUI_X_TELEMETRY_DISABLED__ = undefined; - sinon.stub(ponyfillGlobal, '__MUI_X_TELEMETRY_DISABLED__').value(true); + vi.stubGlobal('__MUI_X_TELEMETRY_DISABLED__', true); expect(getTelemetryEnvConfig(true).IS_COLLECTING).equal(false); }); it('should be enabled if global.__MUI_X_TELEMETRY_DISABLED__ is set to `0`', () => { ponyfillGlobal.__MUI_X_TELEMETRY_DISABLED__ = undefined; - sinon.stub(ponyfillGlobal, '__MUI_X_TELEMETRY_DISABLED__').value(false); + vi.stubGlobal('__MUI_X_TELEMETRY_DISABLED__', false); expect(getTelemetryEnvConfig(true).IS_COLLECTING).equal(true); }); @@ -72,12 +70,10 @@ describe('Telemetry: getTelemetryConfig', () => { }); it('debug should be enabled if env MUI_X_TELEMETRY_DEBUG is set to `1`', () => { - sinon.stub(process, 'env').value({ MUI_X_TELEMETRY_DEBUG: '1' }); - process.stdout.write(`${JSON.stringify(getTelemetryEnvConfig(true), null, 2)}\n`); + vi.stubEnv('MUI_X_TELEMETRY_DEBUG', '1'); expect(getTelemetryEnvConfig(true).DEBUG).equal(true); - sinon.stub(process, 'env').value({ MUI_X_TELEMETRY_DEBUG: '0' }); - process.stdout.write(`${JSON.stringify(getTelemetryEnvConfig(true), null, 2)}\n`); + vi.stubEnv('MUI_X_TELEMETRY_DEBUG', '0'); expect(getTelemetryEnvConfig(true).DEBUG).equal(false); }); }); diff --git a/packages/x-telemetry/tsconfig.json b/packages/x-telemetry/tsconfig.json index 827e5bc9734ed..5130f461f7f4f 100644 --- a/packages/x-telemetry/tsconfig.json +++ b/packages/x-telemetry/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { + "skipLibCheck": true, "types": ["@mui/internal-test-utils/initMatchers", "chai-dom", "mocha", "node", "vite/client"] }, "include": ["src/**/*"] diff --git a/packages/x-telemetry/vitest.config.browser.mts b/packages/x-telemetry/vitest.config.browser.mts new file mode 100644 index 0000000000000..1b6f538191a3a --- /dev/null +++ b/packages/x-telemetry/vitest.config.browser.mts @@ -0,0 +1,19 @@ +/// +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'browser', + browser: { + enabled: true, + instances: [ + { + browser: 'chromium', + }, + ], + }, + }, +}); diff --git a/packages/x-telemetry/vitest.config.jsdom.mts b/packages/x-telemetry/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..50dd48e254699 --- /dev/null +++ b/packages/x-telemetry/vitest.config.jsdom.mts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + }, +}); diff --git a/packages/x-tree-view-pro/vitest.config.browser.mts b/packages/x-tree-view-pro/vitest.config.browser.mts new file mode 100644 index 0000000000000..1b6f538191a3a --- /dev/null +++ b/packages/x-tree-view-pro/vitest.config.browser.mts @@ -0,0 +1,19 @@ +/// +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'browser', + browser: { + enabled: true, + instances: [ + { + browser: 'chromium', + }, + ], + }, + }, +}); diff --git a/packages/x-tree-view-pro/vitest.config.jsdom.mts b/packages/x-tree-view-pro/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..50dd48e254699 --- /dev/null +++ b/packages/x-tree-view-pro/vitest.config.jsdom.mts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + }, +}); diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx index a5e54a8063fef..0105373eb4bbc 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx @@ -7,6 +7,7 @@ import { act, fireEvent } from '@mui/internal-test-utils'; import { TreeItem, TreeItemProps } from '@mui/x-tree-view/TreeItem'; import { UseTreeItemContentSlotOwnProps } from '@mui/x-tree-view/useTreeItem'; import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; +import { clearWarningsCache } from '@mui/x-internals/warning'; /** * All tests related to keyboard navigation (e.g.: expanding using "Enter" and "ArrowRight") @@ -14,6 +15,10 @@ import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; */ describeTreeView<[UseTreeViewExpansionSignature]>('useTreeViewExpansion plugin', ({ render }) => { describe('model props (expandedItems, defaultExpandedItems, onExpandedItemsChange)', () => { + beforeEach(() => { + clearWarningsCache(); + }); + it('should not expand items when no default state and no control state are defined', () => { const view = render({ items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.test.tsx index 2e0ceafdefdf1..f4babee7a6bd6 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.test.tsx @@ -29,8 +29,8 @@ describeTreeView< 'MUI X: The Tree View component requires all items to have a unique `id` property.', reactMajor < 19 && 'MUI X: The Tree View component requires all items to have a unique `id` property.', - reactMajor < 19 && `The above error occurred in the component`, - reactMajor < 19 && `The above error occurred in the component`, + reactMajor < 19 && `The above error occurred in the component`, + reactMajor < 19 && `The above error occurred in the component`, ]); } else { expect(() => @@ -40,7 +40,7 @@ describeTreeView< reactMajor < 19 && 'MUI X: The Tree View component requires all items to have a unique `id` property.', reactMajor < 19 && - `The above error occurred in the component`, + `The above error occurred in the component`, ]); } }); diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.test.tsx index 9bf2012fb2bf5..1c79ef97b0176 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.test.tsx @@ -6,6 +6,7 @@ import { UseTreeViewExpansionSignature, UseTreeViewSelectionSignature, } from '@mui/x-tree-view/internals'; +import { clearWarningsCache } from '@mui/x-internals/warning'; /** * All tests related to keyboard navigation (e.g.: selection using "Space") @@ -15,6 +16,10 @@ describeTreeView<[UseTreeViewSelectionSignature, UseTreeViewExpansionSignature]> 'useTreeViewSelection plugin', ({ render }) => { describe('model props (selectedItems, defaultSelectedItems, onSelectedItemsChange)', () => { + beforeEach(() => { + clearWarningsCache(); + }); + it('should not select items when no default state and no control state are defined', () => { const view = render({ items: [{ id: '1' }, { id: '2' }], diff --git a/packages/x-tree-view/vitest.config.browser.mts b/packages/x-tree-view/vitest.config.browser.mts new file mode 100644 index 0000000000000..1b6f538191a3a --- /dev/null +++ b/packages/x-tree-view/vitest.config.browser.mts @@ -0,0 +1,19 @@ +/// +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'browser', + browser: { + enabled: true, + instances: [ + { + browser: 'chromium', + }, + ], + }, + }, +}); diff --git a/packages/x-tree-view/vitest.config.jsdom.mts b/packages/x-tree-view/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..50dd48e254699 --- /dev/null +++ b/packages/x-tree-view/vitest.config.jsdom.mts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + }, +}); diff --git a/patches/babel-plugin-replace-imports@1.0.2.patch b/patches/babel-plugin-replace-imports@1.0.2.patch deleted file mode 100644 index be94600572cda..0000000000000 --- a/patches/babel-plugin-replace-imports@1.0.2.patch +++ /dev/null @@ -1,8 +0,0 @@ -diff --git a/lib/index.js b/lib/index.js -index 8da63adc141b73774c50363b31ee00001ccd9156..db49ed11479699f329f6b0bfb70fa73d6ad960e2 100644 ---- a/lib/index.js -+++ b/lib/index.js -@@ -1 +1 @@ --'use strict';Object.defineProperty(exports,'__esModule',{value:!0}),exports.optionLabels=void 0,exports.getErrorMessage=getErrorMessage;var _lodash=require('lodash.isempty'),_lodash2=_interopRequireDefault(_lodash),_lodash3=require('lodash.isstring'),_lodash4=_interopRequireDefault(_lodash3),_lodash5=require('lodash.isregexp'),_lodash6=_interopRequireDefault(_lodash5),_lodash7=require('lodash.isobject'),_lodash8=_interopRequireDefault(_lodash7),_lodash9=require('lodash.isfunction'),_lodash10=_interopRequireDefault(_lodash9);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}var PLUGIN='babel-plugin-replace-imports',ERRORS={0:'options are required.',1:'option is required.',2:'option must be a RegExp.',3:'option must be a String or a Function',4:'options item must be an Object.'},optionLabels=exports.optionLabels={test:'test',replacer:'replacer'};function getErrorMessage(code,text){var msg=((text?'\xAB'+text+'\xBB':'')+' '+ERRORS[code]).trim();return'\n'+PLUGIN+': '+msg}function init(_ref){function throwError(code,text){var msg=getErrorMessage(code,text);throw new Error(msg)}function getOption(option){return(!(0,_lodash8.default)(option)||(0,_lodash6.default)(option)||Array.isArray(option))&&throwError(4),option}function getTestOption(option){return!(0,_lodash6.default)(option)&&(0,_lodash2.default)(option)&&throwError(1,optionLabels.test),(0,_lodash6.default)(option)||throwError(2,optionLabels.test),option}function getReplacerListOption(option){return(0,_lodash10.default)(option)?[option]:((0,_lodash2.default)(option)&&throwError(1,optionLabels.replacer),Array.isArray(option)?option:[option])}function getReplacerOption(option){return(0,_lodash4.default)(option)||(0,_lodash10.default)(option)||throwError(3,optionLabels.replacer),option}var types=_ref.types;return{visitor:{ImportDeclaration:function ImportDeclaration(path,_ref2){var opts=_ref2.opts;if(!path.node.__processed){(0,_lodash2.default)(opts)&&throwError(0);var source=path.node.source.value,transforms=[],options=opts;Array.isArray(options)||(options=[opts]);for(var _ret,_loop=function(i){var opt=getOption(options[i]),regex=getTestOption(opt[optionLabels.test]);if(regex.test(source)){var replacerList=getReplacerListOption(opt[optionLabels.replacer]);return replacerList.forEach(function(replacer){var repl=getReplacerOption(replacer),importDeclaration=types.importDeclaration(path.node.specifiers,types.stringLiteral(source.replace(regex,repl)));importDeclaration.__processed=!0,transforms.push(importDeclaration)}),'break'}},i=0;i=18'} + '@bundled-es-modules/cookie@2.0.1': resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} @@ -2815,152 +2831,152 @@ packages: resolution: {integrity: sha512-xjZTSFgECpb9Ohuk5yMX5RhUEbfeQcuOp8IF60e+wyzWEF0M5xeSgqsfLtvPEX8BIyOX9saZqzuGPmZ8oWc+5Q==} engines: {node: '>=16'} - '@esbuild/aix-ppc64@0.25.2': - resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} + '@esbuild/aix-ppc64@0.25.3': + resolution: {integrity: sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.2': - resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} + '@esbuild/android-arm64@0.25.3': + resolution: {integrity: sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.2': - resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} + '@esbuild/android-arm@0.25.3': + resolution: {integrity: sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.2': - resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} + '@esbuild/android-x64@0.25.3': + resolution: {integrity: sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.2': - resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} + '@esbuild/darwin-arm64@0.25.3': + resolution: {integrity: sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.2': - resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} + '@esbuild/darwin-x64@0.25.3': + resolution: {integrity: sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.2': - resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} + '@esbuild/freebsd-arm64@0.25.3': + resolution: {integrity: sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.2': - resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} + '@esbuild/freebsd-x64@0.25.3': + resolution: {integrity: sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.2': - resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} + '@esbuild/linux-arm64@0.25.3': + resolution: {integrity: sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.2': - resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} + '@esbuild/linux-arm@0.25.3': + resolution: {integrity: sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.2': - resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} + '@esbuild/linux-ia32@0.25.3': + resolution: {integrity: sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.2': - resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} + '@esbuild/linux-loong64@0.25.3': + resolution: {integrity: sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.2': - resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} + '@esbuild/linux-mips64el@0.25.3': + resolution: {integrity: sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.2': - resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} + '@esbuild/linux-ppc64@0.25.3': + resolution: {integrity: sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.2': - resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} + '@esbuild/linux-riscv64@0.25.3': + resolution: {integrity: sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.2': - resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} + '@esbuild/linux-s390x@0.25.3': + resolution: {integrity: sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.2': - resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} + '@esbuild/linux-x64@0.25.3': + resolution: {integrity: sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.2': - resolution: {integrity: sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==} + '@esbuild/netbsd-arm64@0.25.3': + resolution: {integrity: sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.2': - resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} + '@esbuild/netbsd-x64@0.25.3': + resolution: {integrity: sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.2': - resolution: {integrity: sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==} + '@esbuild/openbsd-arm64@0.25.3': + resolution: {integrity: sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.2': - resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} + '@esbuild/openbsd-x64@0.25.3': + resolution: {integrity: sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.25.2': - resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} + '@esbuild/sunos-x64@0.25.3': + resolution: {integrity: sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.2': - resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} + '@esbuild/win32-arm64@0.25.3': + resolution: {integrity: sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.2': - resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} + '@esbuild/win32-ia32@0.25.3': + resolution: {integrity: sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.2': - resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} + '@esbuild/win32-x64@0.25.3': + resolution: {integrity: sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -4659,6 +4675,15 @@ packages: webdriverio: optional: true + '@vitest/coverage-v8@3.1.2': + resolution: {integrity: sha512-XDdaDOeaTMAMYW7N63AqoK32sYUWbXnTkC6tEbVcu3RlU1bB9of32T+PGf8KZvxqLNqeXhafDFqCkwpf2+dyaQ==} + peerDependencies: + '@vitest/browser': 3.1.2 + vitest: 3.1.2 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/expect@3.1.2': resolution: {integrity: sha512-O8hJgr+zREopCAqWl3uCVaOdqJwZ9qaDwUP7vy3Xigad0phZe9APxKhPcDNqYYi0rX5oMvwJMSCAXY2afqeTSA==} @@ -5124,9 +5149,6 @@ packages: babel-plugin-react-remove-properties@0.3.0: resolution: {integrity: sha512-vbxegtXGyVcUkCvayLzftU95vuvpYFV85pRpeMpohMHeEY46Qe0VNWfkVVcCbaZ12CXHzDFOj0esumATcW83ng==} - babel-plugin-replace-imports@1.0.2: - resolution: {integrity: sha512-v+9S4FBg9wYit3c+bDxhRHv/pnhBhhneZOPvqT1c293SSjUuUy1MPK76TsJ9038fp/SD2/TNcqG5PUBoG9/ByQ==} - babel-plugin-search-and-replace@1.1.1: resolution: {integrity: sha512-fjP2VTF3mxxOUnc96mdK22llH92A6gu7A5AFapJmgnqsQi3bqLduIRP0FpA2r5vRZOYPpnX4rE5izQlpsMBjSA==} @@ -5325,8 +5347,8 @@ packages: camelize@1.0.1: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} - caniuse-lite@1.0.30001707: - resolution: {integrity: sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==} + caniuse-lite@1.0.30001703: + resolution: {integrity: sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==} chai-dom@1.12.1: resolution: {integrity: sha512-tvz+D0PJue2VHXRec3udgP/OeeXBiePU3VH6JhEnHQJYzvNzR2nUvEykA9dXVS76JvaUENSOYH8Ufr0kZSnlCQ==} @@ -5369,6 +5391,10 @@ packages: resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + chance@1.1.12: resolution: {integrity: sha512-vVBIGQVnwtUG+SYe0ge+3MvF78cvSpuCOEUJr7sVEk2vSBuMW6OXNJjSzdtzrlxNUEaoqH2GBd5Y/+18BEB01Q==} @@ -6239,8 +6265,8 @@ packages: es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - esbuild@0.25.2: - resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} + esbuild@0.25.3: + resolution: {integrity: sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==} engines: {node: '>=18'} hasBin: true @@ -7478,6 +7504,10 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + istanbul-reports@3.1.7: resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} engines: {node: '>=8'} @@ -7903,9 +7933,6 @@ packages: lodash.isboolean@3.0.3: resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} - lodash.isempty@4.4.0: - resolution: {integrity: sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==} - lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. @@ -7931,9 +7958,6 @@ packages: lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - lodash.isregexp@4.0.1: - resolution: {integrity: sha512-rw9+95tYcUa9nQ1FgdtKvO+hReLGNqnNMHfLq8SwK5Mo6D0R0tIsnRHGHaTHSKeYBaLCJ1JvXWdz4UmpPZ2bag==} - lodash.isstring@4.0.1: resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} @@ -8022,6 +8046,9 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + make-array@1.0.5: resolution: {integrity: sha512-sgK2SAzxT19rWU+qxKUcn6PAh/swiIiz2F8C2cZjLc1z4iwYIfdoihqFIDQ8BDzAGtWPYJ6Sr13K1j/DXynDLA==} engines: {node: '>=0.10.0'} @@ -8418,8 +8445,8 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + nanoid@3.3.9: + resolution: {integrity: sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -10031,6 +10058,10 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + text-extensions@1.9.0: resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} engines: {node: '>=0.10'} @@ -10492,6 +10523,12 @@ packages: yaml: optional: true + vitest-fail-on-console@0.7.1: + resolution: {integrity: sha512-/PjuonFu7CwUVrKaiQPIGXOtiEv2/Gz3o8MbLmovX9TGDxoRCctRC8CA9zJMRUd6AvwGu/V5a3znObTmlPNTgw==} + peerDependencies: + vite: '>=4.5.2' + vitest: '>=0.26.2' + vitest@3.1.2: resolution: {integrity: sha512-WaxpJe092ID1C0mr+LH9MmNrhfzi8I65EX/NRU/Ld016KqQNRgxSOlGNP1hHN+a/F8L15Mh8klwaF77zR3GeDQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -11796,6 +11833,8 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@bcoe/v8-coverage@1.0.2': {} + '@bundled-es-modules/cookie@2.0.1': dependencies: cookie: 0.7.2 @@ -11988,79 +12027,79 @@ snapshots: esquery: 1.6.0 jsdoc-type-pratt-parser: 4.1.0 - '@esbuild/aix-ppc64@0.25.2': + '@esbuild/aix-ppc64@0.25.3': optional: true - '@esbuild/android-arm64@0.25.2': + '@esbuild/android-arm64@0.25.3': optional: true - '@esbuild/android-arm@0.25.2': + '@esbuild/android-arm@0.25.3': optional: true - '@esbuild/android-x64@0.25.2': + '@esbuild/android-x64@0.25.3': optional: true - '@esbuild/darwin-arm64@0.25.2': + '@esbuild/darwin-arm64@0.25.3': optional: true - '@esbuild/darwin-x64@0.25.2': + '@esbuild/darwin-x64@0.25.3': optional: true - '@esbuild/freebsd-arm64@0.25.2': + '@esbuild/freebsd-arm64@0.25.3': optional: true - '@esbuild/freebsd-x64@0.25.2': + '@esbuild/freebsd-x64@0.25.3': optional: true - '@esbuild/linux-arm64@0.25.2': + '@esbuild/linux-arm64@0.25.3': optional: true - '@esbuild/linux-arm@0.25.2': + '@esbuild/linux-arm@0.25.3': optional: true - '@esbuild/linux-ia32@0.25.2': + '@esbuild/linux-ia32@0.25.3': optional: true - '@esbuild/linux-loong64@0.25.2': + '@esbuild/linux-loong64@0.25.3': optional: true - '@esbuild/linux-mips64el@0.25.2': + '@esbuild/linux-mips64el@0.25.3': optional: true - '@esbuild/linux-ppc64@0.25.2': + '@esbuild/linux-ppc64@0.25.3': optional: true - '@esbuild/linux-riscv64@0.25.2': + '@esbuild/linux-riscv64@0.25.3': optional: true - '@esbuild/linux-s390x@0.25.2': + '@esbuild/linux-s390x@0.25.3': optional: true - '@esbuild/linux-x64@0.25.2': + '@esbuild/linux-x64@0.25.3': optional: true - '@esbuild/netbsd-arm64@0.25.2': + '@esbuild/netbsd-arm64@0.25.3': optional: true - '@esbuild/netbsd-x64@0.25.2': + '@esbuild/netbsd-x64@0.25.3': optional: true - '@esbuild/openbsd-arm64@0.25.2': + '@esbuild/openbsd-arm64@0.25.3': optional: true - '@esbuild/openbsd-x64@0.25.2': + '@esbuild/openbsd-x64@0.25.3': optional: true - '@esbuild/sunos-x64@0.25.2': + '@esbuild/sunos-x64@0.25.3': optional: true - '@esbuild/win32-arm64@0.25.2': + '@esbuild/win32-arm64@0.25.3': optional: true - '@esbuild/win32-ia32@0.25.2': + '@esbuild/win32-ia32@0.25.3': optional: true - '@esbuild/win32-x64@0.25.2': + '@esbuild/win32-x64@0.25.3': optional: true '@eslint-community/eslint-utils@4.5.0(eslint@8.57.1)': @@ -13791,11 +13830,11 @@ snapshots: '@types/use-sync-external-store@1.5.0': {} - '@types/webpack-bundle-analyzer@4.7.0(@swc/core@1.11.21)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7))': + '@types/webpack-bundle-analyzer@4.7.0(@swc/core@1.11.21)(esbuild@0.25.3)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7))': dependencies: '@types/node': 22.15.3 tapable: 2.2.1 - webpack: 5.99.7(@swc/core@1.11.21)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) + webpack: 5.99.7(@swc/core@1.11.21)(esbuild@0.25.3)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) transitivePeerDependencies: - '@swc/core' - esbuild @@ -13942,6 +13981,26 @@ snapshots: - utf-8-validate - vite + '@vitest/coverage-v8@3.1.2(@vitest/browser@3.1.2)(vitest@3.1.2)': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 1.0.2 + debug: 4.4.0(supports-color@8.1.1) + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.17 + magicast: 0.3.5 + std-env: 3.9.0 + test-exclude: 7.0.1 + tinyrainbow: 2.0.0 + vitest: 3.1.2(@types/debug@4.1.12)(@types/node@22.15.3)(@vitest/browser@3.1.2)(@vitest/ui@3.1.2)(jsdom@26.1.0)(lightningcss@1.29.3)(msw@2.7.3(@types/node@22.15.3)(typescript@5.8.3))(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0) + optionalDependencies: + '@vitest/browser': 3.1.2(msw@2.7.3(@types/node@22.15.3)(typescript@5.8.3))(playwright@1.52.0)(vite@6.3.4(@types/node@22.15.3)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0))(vitest@3.1.2) + transitivePeerDependencies: + - supports-color + '@vitest/expect@3.1.2': dependencies: '@vitest/spy': 3.1.2 @@ -14081,17 +14140,17 @@ snapshots: '@webpack-cli/configtest@3.0.1(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7))(webpack@5.99.7)': dependencies: - webpack: 5.99.7(@swc/core@1.11.21)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) + webpack: 5.99.7(@swc/core@1.11.21)(esbuild@0.25.3)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7) '@webpack-cli/info@3.0.1(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7))(webpack@5.99.7)': dependencies: - webpack: 5.99.7(@swc/core@1.11.21)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) + webpack: 5.99.7(@swc/core@1.11.21)(esbuild@0.25.3)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7) '@webpack-cli/serve@3.0.1(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7))(webpack@5.99.7)': dependencies: - webpack: 5.99.7(@swc/core@1.11.21)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) + webpack: 5.99.7(@swc/core@1.11.21)(esbuild@0.25.3)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7) '@xtuc/ieee754@1.2.0': {} @@ -14422,7 +14481,7 @@ snapshots: autoprefixer@10.4.21(postcss@8.5.3): dependencies: browserslist: 4.24.4 - caniuse-lite: 1.0.30001707 + caniuse-lite: 1.0.30001703 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -14449,7 +14508,7 @@ snapshots: dependencies: '@babel/core': 7.27.1 find-up: 5.0.0 - webpack: 5.99.7(@swc/core@1.11.21)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) + webpack: 5.99.7(@swc/core@1.11.21)(esbuild@0.25.3)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) babel-plugin-istanbul@7.0.0: dependencies: @@ -14517,14 +14576,6 @@ snapshots: babel-plugin-react-remove-properties@0.3.0: {} - babel-plugin-replace-imports@1.0.2(patch_hash=779d8690d607924781aaf4f896bfbd209a78755ddd5b9ebbea5ebce3b2919ba0): - dependencies: - lodash.isempty: 4.4.0 - lodash.isfunction: 3.0.9 - lodash.isobject: 3.0.2 - lodash.isregexp: 4.0.1 - lodash.isstring: 4.0.1 - babel-plugin-search-and-replace@1.1.1: {} babel-plugin-transform-import-meta@2.3.2(@babel/core@7.27.1): @@ -14623,7 +14674,7 @@ snapshots: dependencies: ansi-align: 3.0.1 camelcase: 7.0.1 - chalk: 5.0.1 + chalk: 5.3.0 cli-boxes: 3.0.0 string-width: 5.1.2 type-fest: 2.19.0 @@ -14647,7 +14698,7 @@ snapshots: browserslist@4.24.4: dependencies: - caniuse-lite: 1.0.30001707 + caniuse-lite: 1.0.30001703 electron-to-chromium: 1.5.114 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.24.4) @@ -14756,7 +14807,7 @@ snapshots: camelize@1.0.1: {} - caniuse-lite@1.0.30001707: {} + caniuse-lite@1.0.30001703: {} chai-dom@1.12.1(chai@4.5.0): dependencies: @@ -14811,6 +14862,8 @@ snapshots: chalk@5.0.1: {} + chalk@5.3.0: {} + chance@1.1.12: {} character-entities-legacy@3.0.0: {} @@ -14991,7 +15044,7 @@ snapshots: dependencies: schema-utils: 4.3.2 serialize-javascript: 6.0.2 - webpack: 5.99.7(@swc/core@1.11.21)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) + webpack: 5.99.7(@swc/core@1.11.21)(esbuild@0.25.3)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) compression@1.7.4: dependencies: @@ -15750,33 +15803,33 @@ snapshots: es6-error@4.1.1: {} - esbuild@0.25.2: + esbuild@0.25.3: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.2 - '@esbuild/android-arm': 0.25.2 - '@esbuild/android-arm64': 0.25.2 - '@esbuild/android-x64': 0.25.2 - '@esbuild/darwin-arm64': 0.25.2 - '@esbuild/darwin-x64': 0.25.2 - '@esbuild/freebsd-arm64': 0.25.2 - '@esbuild/freebsd-x64': 0.25.2 - '@esbuild/linux-arm': 0.25.2 - '@esbuild/linux-arm64': 0.25.2 - '@esbuild/linux-ia32': 0.25.2 - '@esbuild/linux-loong64': 0.25.2 - '@esbuild/linux-mips64el': 0.25.2 - '@esbuild/linux-ppc64': 0.25.2 - '@esbuild/linux-riscv64': 0.25.2 - '@esbuild/linux-s390x': 0.25.2 - '@esbuild/linux-x64': 0.25.2 - '@esbuild/netbsd-arm64': 0.25.2 - '@esbuild/netbsd-x64': 0.25.2 - '@esbuild/openbsd-arm64': 0.25.2 - '@esbuild/openbsd-x64': 0.25.2 - '@esbuild/sunos-x64': 0.25.2 - '@esbuild/win32-arm64': 0.25.2 - '@esbuild/win32-ia32': 0.25.2 - '@esbuild/win32-x64': 0.25.2 + '@esbuild/aix-ppc64': 0.25.3 + '@esbuild/android-arm': 0.25.3 + '@esbuild/android-arm64': 0.25.3 + '@esbuild/android-x64': 0.25.3 + '@esbuild/darwin-arm64': 0.25.3 + '@esbuild/darwin-x64': 0.25.3 + '@esbuild/freebsd-arm64': 0.25.3 + '@esbuild/freebsd-x64': 0.25.3 + '@esbuild/linux-arm': 0.25.3 + '@esbuild/linux-arm64': 0.25.3 + '@esbuild/linux-ia32': 0.25.3 + '@esbuild/linux-loong64': 0.25.3 + '@esbuild/linux-mips64el': 0.25.3 + '@esbuild/linux-ppc64': 0.25.3 + '@esbuild/linux-riscv64': 0.25.3 + '@esbuild/linux-s390x': 0.25.3 + '@esbuild/linux-x64': 0.25.3 + '@esbuild/netbsd-arm64': 0.25.3 + '@esbuild/netbsd-x64': 0.25.3 + '@esbuild/openbsd-arm64': 0.25.3 + '@esbuild/openbsd-x64': 0.25.3 + '@esbuild/sunos-x64': 0.25.3 + '@esbuild/win32-arm64': 0.25.3 + '@esbuild/win32-ia32': 0.25.3 + '@esbuild/win32-x64': 0.25.3 escalade@3.2.0: {} @@ -15842,7 +15895,7 @@ snapshots: lodash: 4.17.21 resolve: 2.0.0-next.5 semver: 5.7.2 - webpack: 5.99.7(@swc/core@1.11.21)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) + webpack: 5.99.7(@swc/core@1.11.21)(esbuild@0.25.3)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) transitivePeerDependencies: - supports-color @@ -16817,7 +16870,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.1 optionalDependencies: - webpack: 5.99.7(@swc/core@1.11.21)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) + webpack: 5.99.7(@swc/core@1.11.21)(esbuild@0.25.3)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) htmlparser2@6.1.0: dependencies: @@ -17241,6 +17294,14 @@ snapshots: transitivePeerDependencies: - supports-color + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.4.0(supports-color@8.1.1) + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + istanbul-reports@3.1.7: dependencies: html-escaper: 2.0.2 @@ -17522,7 +17583,7 @@ snapshots: dependencies: glob: 7.2.3 minimatch: 9.0.5 - webpack: 5.99.7(@swc/core@1.11.21)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) + webpack: 5.99.7(@swc/core@1.11.21)(esbuild@0.25.3)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) webpack-merge: 4.2.2 karma@6.4.4: @@ -17828,8 +17889,6 @@ snapshots: lodash.isboolean@3.0.3: {} - lodash.isempty@4.4.0: {} - lodash.isequal@4.5.0: {} lodash.isfunction@3.0.9: {} @@ -17846,8 +17905,6 @@ snapshots: lodash.isplainobject@4.0.6: {} - lodash.isregexp@4.0.1: {} - lodash.isstring@4.0.1: {} lodash.isundefined@3.0.1: {} @@ -17925,6 +17982,12 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.27.1 + '@babel/types': 7.27.1 + source-map-js: 1.2.1 + make-array@1.0.5: {} make-dir@2.1.0: @@ -18518,7 +18581,7 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.11: {} + nanoid@3.3.9: {} natural-compare@1.4.0: {} @@ -18538,7 +18601,7 @@ snapshots: '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 - caniuse-lite: 1.0.30001707 + caniuse-lite: 1.0.30001703 postcss: 8.4.31 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) @@ -18706,7 +18769,7 @@ snapshots: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.99.7(@swc/core@1.11.21)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) + webpack: 5.99.7(@swc/core@1.11.21)(esbuild@0.25.3)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) nwsapi@2.2.18: {} @@ -19219,19 +19282,19 @@ snapshots: postcss@8.4.31: dependencies: - nanoid: 3.3.11 + nanoid: 3.3.9 picocolors: 1.1.1 source-map-js: 1.2.1 postcss@8.4.49: dependencies: - nanoid: 3.3.11 + nanoid: 3.3.9 picocolors: 1.1.1 source-map-js: 1.2.1 postcss@8.5.3: dependencies: - nanoid: 3.3.11 + nanoid: 3.3.9 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -20206,7 +20269,7 @@ snapshots: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.99.7(@swc/core@1.11.21)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) + webpack: 5.99.7(@swc/core@1.11.21)(esbuild@0.25.3)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) string-width@4.2.3: dependencies: @@ -20408,16 +20471,17 @@ snapshots: temp-dir@1.0.0: {} - terser-webpack-plugin@5.3.14(@swc/core@1.11.21)(webpack@5.99.7): + terser-webpack-plugin@5.3.14(@swc/core@1.11.21)(esbuild@0.25.3)(webpack@5.99.7): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 terser: 5.39.0 - webpack: 5.99.7(@swc/core@1.11.21)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) + webpack: 5.99.7(@swc/core@1.11.21)(esbuild@0.25.3)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) optionalDependencies: '@swc/core': 1.11.21 + esbuild: 0.25.3 terser@5.39.0: dependencies: @@ -20432,6 +20496,12 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + text-extensions@1.9.0: {} text-table@0.2.0: {} @@ -20550,7 +20620,7 @@ snapshots: tsx@4.19.4: dependencies: - esbuild: 0.25.2 + esbuild: 0.25.3 get-tsconfig: 4.10.0 optionalDependencies: fsevents: 2.3.3 @@ -20853,7 +20923,7 @@ snapshots: vite@6.3.4(@types/node@22.15.3)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0): dependencies: - esbuild: 0.25.2 + esbuild: 0.25.3 fdir: 6.4.4(picomatch@4.0.2) picomatch: 4.0.2 postcss: 8.5.3 @@ -20867,6 +20937,12 @@ snapshots: tsx: 4.19.4 yaml: 2.7.0 + vitest-fail-on-console@0.7.1(vite@6.3.4(@types/node@22.15.3)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0))(vitest@3.1.2): + dependencies: + chalk: 5.3.0 + vite: 6.3.4(@types/node@22.15.3)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0) + vitest: 3.1.2(@types/debug@4.1.12)(@types/node@22.15.3)(@vitest/browser@3.1.2)(@vitest/ui@3.1.2)(jsdom@26.1.0)(lightningcss@1.29.3)(msw@2.7.3(@types/node@22.15.3)(typescript@5.8.3))(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0) + vitest@3.1.2(@types/debug@4.1.12)(@types/node@22.15.3)(@vitest/browser@3.1.2)(@vitest/ui@3.1.2)(jsdom@26.1.0)(lightningcss@1.29.3)(msw@2.7.3(@types/node@22.15.3)(typescript@5.8.3))(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0): dependencies: '@vitest/expect': 3.1.2 @@ -20963,7 +21039,7 @@ snapshots: import-local: 3.2.0 interpret: 3.1.1 rechoir: 0.8.0 - webpack: 5.99.7(@swc/core@1.11.21)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) + webpack: 5.99.7(@swc/core@1.11.21)(esbuild@0.25.3)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)) webpack-merge: 6.0.1 optionalDependencies: webpack-bundle-analyzer: 4.10.2 @@ -20980,7 +21056,7 @@ snapshots: webpack-sources@3.2.3: {} - webpack@5.99.7(@swc/core@1.11.21)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)): + webpack@5.99.7(@swc/core@1.11.21)(esbuild@0.25.3)(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.99.7)): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.7 @@ -21003,7 +21079,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.2 tapable: 2.2.1 - terser-webpack-plugin: 5.3.14(@swc/core@1.11.21)(webpack@5.99.7) + terser-webpack-plugin: 5.3.14(@swc/core@1.11.21)(esbuild@0.25.3)(webpack@5.99.7) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: diff --git a/renovate.json b/renovate.json index 931624193417f..d74896da36c44 100644 --- a/renovate.json +++ b/renovate.json @@ -104,7 +104,7 @@ }, { "groupName": "Vite & Vitest", - "matchPackageNames": ["@vitejs/**", "/vitest/"] + "matchPackageNames": ["@vitejs/**", "/vitest/", "esbuild"] }, { "groupName": "date-fns-v2", diff --git a/scripts/getTestName.mts b/scripts/getTestName.mts new file mode 100644 index 0000000000000..63cab306773b0 --- /dev/null +++ b/scripts/getTestName.mts @@ -0,0 +1,10 @@ +import { readFileSync } from 'node:fs'; + +export const getTestName = (url: string): string => { + const packageJson = readFileSync(new URL('./package.json', url), 'utf-8'); + + const packageJsonParsed = JSON.parse(packageJson); + const packageName = packageJsonParsed.name.split('/')[1] ?? packageJsonParsed.name; + + return packageName; +}; diff --git a/scripts/test.mjs b/scripts/test.mjs deleted file mode 100644 index d2a4b5485de60..0000000000000 --- a/scripts/test.mjs +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint-disable no-console */ -import { spawn } from 'node:child_process'; - -/* -This script ensures that we can use the same commands to run tests -when using pnpm as when using Yarn. -It enables to run `pnpm test` (or `pnpm t`) without any arguments, to run all tests, -or `pnpm test ` (or `pnpm t `) to run a subset of tests in watch mode. - -See https://github.com/mui/mui-x/pull/12948 for more context. -*/ - -if (process.argv.length < 3) { - console.log('Running unit tests...'); - spawn('pnpm', ['test:unit'], { - shell: true, - stdio: ['inherit', 'inherit', 'inherit'], - env: { - ...process.env, - TZ: 'UTC', - }, - }); -} else { - console.log('Running selected tests in watch mode...'); - console.warn( - 'Note: run `pnpm tc` to have a better experience (and be able to pass in additional parameters).', - ); - - console.log('cmd', ['tc', ...process.argv.slice(2)]); - - spawn('pnpm', ['tc', ...process.argv.slice(2)], { - shell: true, - stdio: ['inherit', 'inherit', 'inherit'], - env: { - ...process.env, - TZ: 'UTC', - }, - }); -} diff --git a/test/cli.js b/test/cli.js deleted file mode 100644 index 509892a5809c7..0000000000000 --- a/test/cli.js +++ /dev/null @@ -1,126 +0,0 @@ -const childProcess = require('child_process'); -const fs = require('fs'); -const glob = require('fast-glob'); -const path = require('path'); -const yargs = require('yargs'); - -async function run(argv) { - const workspaceRoot = path.resolve(__dirname, '../'); - - const gitignore = fs.readFileSync(path.join(workspaceRoot, '.gitignore'), { encoding: 'utf-8' }); - const ignore = gitignore - .split(/\r?\n/) - .filter((pattern) => { - return pattern.length > 0 && !pattern.startsWith('#'); - }) - .map((line) => { - if (line.startsWith('/')) { - // "/" marks the cwd of the ignore file. - // Since we declare the dirname of the gitignore the cwd we can prepend "." as a shortcut. - return `.${line}`; - } - return line; - }); - const globPattern = `**/*${argv.testFilePattern.replace(/\\/g, '/')}*`; - const spec = glob - .sync(globPattern, { - cwd: workspaceRoot, - ignore, - followSymbolicLinks: false, - }) - .filter((relativeFile) => { - return /\.test\.(js|ts|tsx)$/.test(relativeFile); - }); - - if (spec.length === 0) { - throw new Error(`Could not find any file test files matching '${globPattern}'`); - } - - const args = ['mocha'].concat(spec); - if (argv.bail) { - args.push('--bail'); - } - if (argv.debug || argv.inspecting) { - args.push('--timeout 0'); - } - if (argv.debug) { - args.push('--inspect-brk'); - } - if (!argv.single) { - args.push('--watch'); - } - if (argv.testNamePattern !== undefined) { - args.push(`--grep '${argv.testNamePattern}'`); - } - - const mochaProcess = childProcess.spawn('pnpm', args, { - env: { - ...process.env, - BABEL_ENV: 'test', - NODE_ENV: argv.production ? 'production' : 'test', - TZ: 'UTC', - }, - shell: true, - stdio: ['inherit', 'inherit', 'inherit'], - }); - - mochaProcess.once('exit', (signal) => { - process.exit(signal !== null ? signal : undefined); - }); - - process.on('SIGINT', () => { - // Forward interrupt. - // Otherwise cli.js exits and the you get dangling console output from mocha. - // "dangling" meaning that you get mocha output in the new terminal input. - mochaProcess.kill('SIGINT'); - }); -} - -yargs - .command({ - command: '$0 ', - description: 'Test cli for developing', - builder: (command) => { - return command - .positional('testFilePattern', { - description: 'Only test files match "**/*{testFilePattern}*.test.{js,ts,tsx}"', - type: 'string', - }) - .option('bail', { - alias: 'b', - description: 'Stop on first error.', - type: 'boolean', - }) - .option('debug', { - alias: 'd', - description: - 'Allows attaching a debugger and waits for the debugger to start code execution.', - type: 'boolean', - }) - .option('inspecting', { - description: 'In case you expect to hit breakpoints that may interrupt a test.', - type: 'boolean', - }) - .option('production', { - alias: 'p', - description: - 'Run tests in production environment. WARNING: Will not work with most tests.', - type: 'boolean', - }) - .option('single', { - alias: 's', - description: 'Run only once i.e. not in watch mode.', - type: 'boolean', - }) - .option('testNamePattern', { - alias: 't', - description: 'Limit tests by their name given a pattern.', - type: 'string', - }); - }, - handler: run, - }) - .help() - .strict(true) - .version(false) - .parse(); diff --git a/test/setupVitest.ts b/test/setupVitest.ts new file mode 100644 index 0000000000000..f76a851fb71c1 --- /dev/null +++ b/test/setupVitest.ts @@ -0,0 +1,96 @@ +import { beforeAll, afterAll } from 'vitest'; +import 'test/utils/addChaiAssertions'; +import 'test/utils/setupPickers'; +import 'test/utils/licenseRelease'; +import { generateTestLicenseKey, setupTestLicenseKey } from 'test/utils/testLicense'; +import { configure } from '@mui/internal-test-utils'; +import { config } from 'react-transition-group'; + +import sinon from 'sinon'; +import { unstable_resetCleanupTracking as unstable_resetCleanupTrackingDataGrid } from '@mui/x-data-grid'; +import { unstable_resetCleanupTracking as unstable_resetCleanupTrackingDataGridPro } from '@mui/x-data-grid-pro'; +import { unstable_resetCleanupTracking as unstable_resetCleanupTrackingTreeView } from '@mui/x-tree-view'; +import { unstable_cleanupDOM as unstable_cleanupDOMCharts } from '@mui/x-charts/internals'; +import failOnConsole from 'vitest-fail-on-console'; +import { clearWarningsCache } from '@mui/x-internals/warning'; +import { isJSDOM } from './utils/skipIf'; + +// Core's setupVitest is causing issues with the test setup +// import '@mui/internal-test-utils/setupVitest'; + +// Enable missing act warnings: https://github.com/reactwg/react-18/discussions/102 +(globalThis as any).jest = null; +(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; + +let licenseKey: string = ''; + +beforeAll(() => { + licenseKey = generateTestLicenseKey(); +}); + +beforeEach(() => { + clearWarningsCache(); + setupTestLicenseKey(licenseKey); + config.disabled = true; +}); + +afterEach(() => { + unstable_resetCleanupTrackingDataGrid(); + unstable_resetCleanupTrackingDataGridPro(); + unstable_resetCleanupTrackingTreeView(); + unstable_cleanupDOMCharts(); + + // Restore Sinon default sandbox to avoid memory leak + // See https://github.com/sinonjs/sinon/issues/1866 + sinon.restore(); + config.disabled = false; +}); + +configure({ + // JSDOM logs errors otherwise on `getComputedStyle(element, pseudoElement)` calls. + computedStyleSupportsPseudoElements: !isJSDOM, +}); + +failOnConsole(); + +if (!globalThis.before) { + (globalThis as any).before = beforeAll; +} +if (!globalThis.after) { + (globalThis as any).after = afterAll; +} + +const isJsdom = typeof window !== 'undefined' && window.navigator.userAgent.includes('jsdom'); + +// Only necessary when not in browser mode. +if (isJsdom) { + class Touch { + instance: any; + + constructor(instance: any) { + this.instance = instance; + } + + get identifier() { + return this.instance.identifier; + } + + get pageX() { + return this.instance.pageX; + } + + get pageY() { + return this.instance.pageY; + } + + get clientX() { + return this.instance.clientX; + } + + get clientY() { + return this.instance.clientY; + } + } + // @ts-expect-error + globalThis.window.Touch = Touch; +} diff --git a/test/utils/pickers/createPickerRenderer.tsx b/test/utils/pickers/createPickerRenderer.tsx index 4486c900675de..0630c430ac523 100644 --- a/test/utils/pickers/createPickerRenderer.tsx +++ b/test/utils/pickers/createPickerRenderer.tsx @@ -1,10 +1,11 @@ import * as React from 'react'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { createRenderer, CreateRendererOptions, RenderOptions } from '@mui/internal-test-utils'; -import sinon from 'sinon'; +import { vi } from 'vitest'; import { AdapterClassToUse, AdapterName, adapterToUse, availableAdapters } from './adapters'; -interface CreatePickerRendererOptions extends CreateRendererOptions { +interface CreatePickerRendererOptions + extends Omit { // Set-up locale with date-fns object. Other are deduced from `locale.code` locale?: { code: string } | any; adapterName?: AdapterName; @@ -15,29 +16,21 @@ export function createPickerRenderer({ locale, adapterName, instance, - clock: inClock, clockConfig, ...createRendererOptions }: CreatePickerRendererOptions = {}) { - // TODO: Temporary until vitest is enabled - // If only clockConfig='2020/02/20' is provided, we just fake the Date, not the timers - // Most of the time we are using the clock we just want to fake the Date - // If timers are faked it can create inconsistencies with the tests. - // In some cases it also prevents us from really testing the real behavior of the component. - if (!inClock && clockConfig) { - let timer: sinon.SinonFakeTimers | null = null; - beforeEach(() => { - timer = sinon.useFakeTimers({ now: clockConfig, toFake: ['Date'] }); - }); - afterEach(() => { - timer?.restore(); - }); - } - - const { clock, render: clientRender } = createRenderer({ + const { render: clientRender } = createRenderer({ ...createRendererOptions, - // TODO: Temporary until vitest is enabled - ...(inClock ? { clock: inClock, clockConfig } : {}), + }); + beforeEach(() => { + if (clockConfig) { + vi.setSystemTime(clockConfig); + } + }); + afterEach(() => { + if (clockConfig) { + vi.useRealTimers(); + } }); let adapterLocale = [ @@ -67,7 +60,6 @@ export function createPickerRenderer({ } return { - clock, render(node: React.ReactElement, options?: Omit) { return clientRender(node, { ...options, wrapper: Wrapper }); }, diff --git a/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx b/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx index 5a86fa33339f5..8d219cde1a523 100644 --- a/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx +++ b/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import { expect } from 'chai'; -import { SinonFakeTimers, useFakeTimers } from 'sinon'; import { screen } from '@mui/internal-test-utils'; import { adapterToUse } from 'test/utils/pickers'; import { describeSkipIf } from 'test/utils/skipIf'; +import { vi } from 'vitest'; import { DescribeRangeValidationTestSuite } from './describeRangeValidation.types'; const isDisabled = (el: HTMLElement) => el.getAttribute('disabled') !== null; @@ -68,14 +68,14 @@ export const testDayViewRangeValidation: DescribeRangeValidationTestSuite = ( }); describe('with fake timers', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; beforeEach(() => { - timer = useFakeTimers({ now: new Date(2018, 0, 1), toFake: ['Date'] }); + vi.setSystemTime(new Date(2018, 0, 5)); }); + afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); + it('should apply disablePast', () => { const { render } = getOptions(); diff --git a/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx b/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx index 3236b438cdaf4..bc8d308f46161 100644 --- a/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx +++ b/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import { expect } from 'chai'; -import { SinonFakeTimers, spy, useFakeTimers } from 'sinon'; +import { spy } from 'sinon'; import { adapterToUse, getAllFieldInputRoot } from 'test/utils/pickers'; import { act } from '@mui/internal-test-utils/createRenderer'; import { describeSkipIf, testSkipIf } from 'test/utils/skipIf'; +import { vi } from 'vitest'; import { DescribeRangeValidationTestSuite } from './describeRangeValidation.types'; const testInvalidStatus = ( @@ -99,14 +100,14 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu testInvalidStatus([true, false], fieldType); }); describe('with fake timers', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; beforeEach(() => { - timer = useFakeTimers({ now: new Date(2018, 0, 1), toFake: ['Date'] }); + vi.setSystemTime(new Date(2018, 0, 1)); }); + afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); + it('should apply disablePast', () => { const onErrorMock = spy(); const now = adapterToUse.date(); diff --git a/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx b/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx index 6da9c85a50b06..3fd78a254ebb2 100644 --- a/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx +++ b/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx @@ -1,8 +1,9 @@ import * as React from 'react'; import { expect } from 'chai'; -import { SinonFakeTimers, spy, useFakeTimers } from 'sinon'; +import { spy } from 'sinon'; import { adapterToUse, getAllFieldInputRoot } from 'test/utils/pickers'; import { describeSkipIf, testSkipIf } from 'test/utils/skipIf'; +import { vi } from 'vitest'; import { DescribeRangeValidationTestSuite } from './describeRangeValidation.types'; const testInvalidStatus = ( @@ -188,17 +189,16 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( testInvalidStatus([true, false], fieldType); }); - describe('with fake timers', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; + describe('with fake timer', () => { beforeEach(() => { - timer = useFakeTimers({ now: new Date(2018, 0, 1), toFake: ['Date'] }); + vi.setSystemTime(new Date(2018, 0, 1)); }); + afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); - it('should apply disablePast', async () => { + it('should apply disablePast', () => { const onErrorMock = spy(); let now; function WithFakeTimer(props: any) { diff --git a/test/utils/pickers/describeValidation/testTextFieldValidation.tsx b/test/utils/pickers/describeValidation/testTextFieldValidation.tsx index da24ced2703cc..f73a3f6be42de 100644 --- a/test/utils/pickers/describeValidation/testTextFieldValidation.tsx +++ b/test/utils/pickers/describeValidation/testTextFieldValidation.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import { expect } from 'chai'; -import { spy, useFakeTimers, SinonFakeTimers } from 'sinon'; +import { spy } from 'sinon'; import { TimeView } from '@mui/x-date-pickers/models'; import { adapterToUse, getFieldInputRoot } from 'test/utils/pickers'; import { describeSkipIf, testSkipIf } from 'test/utils/skipIf'; +import { vi } from 'vitest'; import { DescribeValidationTestSuite } from './describeValidation.types'; export const testTextFieldValidation: DescribeValidationTestSuite = (ElementToTest, getOptions) => { @@ -138,13 +139,12 @@ export const testTextFieldValidation: DescribeValidationTestSuite = (ElementToTe }); describeSkipIf(!withDate)('with fake timers', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; beforeEach(() => { - timer = useFakeTimers({ now: new Date(2018, 0, 1), toFake: ['Date'] }); + vi.setSystemTime(new Date(2018, 0, 1)); }); + afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); it('should apply disablePast', () => { diff --git a/test/utils/pickers/describeValue/testPickerActionBar.tsx b/test/utils/pickers/describeValue/testPickerActionBar.tsx index 04d7dc4c4f52a..5815cfbe1bc06 100644 --- a/test/utils/pickers/describeValue/testPickerActionBar.tsx +++ b/test/utils/pickers/describeValue/testPickerActionBar.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { SinonFakeTimers, spy, useFakeTimers } from 'sinon'; +import { spy } from 'sinon'; import { fireEvent, screen } from '@mui/internal-test-utils'; import { PickerRangeValue } from '@mui/x-date-pickers/internals'; import { @@ -9,6 +9,7 @@ import { expectPickerChangeHandlerValue, isPickerRangeType, } from 'test/utils/pickers'; +import { vi } from 'vitest'; import { DescribeValueTestSuite } from './describeValue.types'; export const testPickerActionBar: DescribeValueTestSuite = ( @@ -222,13 +223,12 @@ export const testPickerActionBar: DescribeValueTestSuite = ( }); describe('today action', () => { - // TODO: temporary for vitest. Can move to `vi.useFakeTimers` - let timer: SinonFakeTimers | null = null; beforeEach(() => { - timer = useFakeTimers({ now: new Date(2018, 0, 1), toFake: ['Date'] }); + vi.setSystemTime(new Date(2018, 0, 1)); }); + afterEach(() => { - timer?.restore(); + vi.useRealTimers(); }); it("should call onClose, onChange with today's value and onAccept with today's value", () => { diff --git a/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx b/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx index d565d0c22c5ce..87d15f9f377f8 100644 --- a/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx +++ b/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx @@ -192,9 +192,7 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite { const onChange = spy(); const onAccept = spy(); const onClose = spy(); diff --git a/test/utils/pickers/misc.ts b/test/utils/pickers/misc.ts index 05892985870ed..040e263f82111 100644 --- a/test/utils/pickers/misc.ts +++ b/test/utils/pickers/misc.ts @@ -1,16 +1,22 @@ import sinon from 'sinon'; import { MuiPickersAdapter, PickerValidDate } from '@mui/x-date-pickers/models'; +import { onTestFinished } from 'vitest'; import { PickerComponentFamily } from './describe.types'; import { OpenPickerParams } from './openPicker'; -export const stubMatchMedia = (matches = true) => - sinon.stub().returns({ +export const stubMatchMedia = (matches = true) => { + const original = window.matchMedia; + window.matchMedia = sinon.stub().returns({ matches, addListener: () => {}, addEventListener: () => {}, removeListener: () => {}, removeEventListener: () => {}, }); + onTestFinished(() => { + window.matchMedia = original; + }); +}; const getChangeCountForComponentFamily = (componentFamily: PickerComponentFamily) => { switch (componentFamily) { diff --git a/test/utils/skipIf.ts b/test/utils/skipIf.ts index a189e7a99d8ba..36d64a4b61270 100644 --- a/test/utils/skipIf.ts +++ b/test/utils/skipIf.ts @@ -23,4 +23,3 @@ export const isJSDOM = /jsdom/.test(window.navigator.userAgent); export const isOSX = /macintosh/i.test(window.navigator.userAgent); export const hasTouchSupport = typeof window.Touch !== 'undefined' && typeof window.TouchEvent !== 'undefined'; -export const isVitest = process.env.VITEST === 'true'; diff --git a/test/vite-plugin-filter-replace.mts b/test/vite-plugin-filter-replace.mts new file mode 100644 index 0000000000000..f4679ae0b2bb8 --- /dev/null +++ b/test/vite-plugin-filter-replace.mts @@ -0,0 +1,89 @@ +import { Plugin } from 'vite'; +import path from 'path'; + +interface RedirectRule { + /** + * A regex to test importer path against. + * + * Eg: /\\/AdapterDateFnsV2\\// will match `/src/AdapterDateFnsV2/index.js` + */ + test: RegExp; + /** + * The import path to match. Any import path that starts with this will be redirected. + * + * Eg: 'date-fns' will match `import { format } from 'date-fns'` and `import { format } from 'date-fns/addDays'` + */ + from: string; + /** + * The import path to redirect to. This will be replaced in place of `from`. + * + * Eg: 'date-fns-v2' will redirect `import { format } from 'date-fns'` to `import { format } from 'date-fns-v2'` + */ + to: string; + /** + * An array of import paths to include. Use this to force the inclusion of certain dependencies. + * This is useful when you want to include a dependency that is not included by default. + * + * Eg: ['date-fns-v2/**\/*.js'] + */ + include?: string[]; +} + +const cleanDepName = (name: string) => { + // If the name starts with '@', we need to split it into scope and lib + // e.g. `@mui/material/Button` -> `@mui/material` + if (name.startsWith('@')) { + const [scope, lib] = name.split('/'); + return `${scope}/${lib}`; + } + // If the name does not start with '@', we only care about the first part + // e.g. `material/Button` -> `material` + return name.split('/')[0]; +}; + +export function redirectImports(rules: RedirectRule[]): Plugin { + return { + name: 'vite-plugin-redirect-imports', + enforce: 'pre', + + config(config) { + config.optimizeDeps ??= {}; + config.optimizeDeps.include ??= []; + + const depsToInclude = new Set([ + ...rules.flatMap((rule) => rule.include ?? []), + ...rules.flatMap((rule) => cleanDepName(rule.to)), + ]); + + // Ignore already-included deps + config.optimizeDeps.include.forEach((dep) => depsToInclude.delete(dep)); + config.optimizeDeps.include.push(...depsToInclude); + }, + + async resolveId(source, importer) { + if (!importer) { + return null; + } + + const normalizedImporter = importer.split(path.sep).join('/'); + + for (const rule of rules) { + if (!rule.test.test(normalizedImporter)) { + continue; + } + + // Match `from` or `from/...` + const match = source === rule.from || source.startsWith(`${rule.from}/`); + if (!match) { + continue; + } + + const newSource = rule.to + source.slice(rule.from.length); + + return this.resolve(newSource, importer, { skipSelf: true }); + } + + return null; + }, + }; +} diff --git a/tsconfig.json b/tsconfig.json index ed9328c304d56..c6238bc0289ea 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "strict": true, "baseUrl": "./", "allowJs": true, + "allowImportingTsExtensions": true, "paths": { "@mui/x-data-grid": ["./packages/x-data-grid/src"], "@mui/x-data-grid/*": ["./packages/x-data-grid/src/*"], @@ -49,7 +50,8 @@ "test/*": ["./test/*"], "docs/*": ["./node_modules/@mui/monorepo/docs/*"], "docsx/*": ["./docs/*"] - } + }, + "types": ["@vitest/browser/providers/playwright"] }, "exclude": ["**/node_modules/!(@mui)/**", "**/build/**/*", "docs/export/**/*"] } diff --git a/vitest.config.mts b/vitest.config.mts new file mode 100644 index 0000000000000..e350ef7758af9 --- /dev/null +++ b/vitest.config.mts @@ -0,0 +1,49 @@ +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { defineConfig } from 'vitest/config'; + +const CURRENT_DIR = dirname(fileURLToPath(import.meta.url)); +const WORKSPACE_ROOT = resolve(CURRENT_DIR, './'); + +declare global { + interface MUIEnv { + JSDOM?: string; + BROWSER?: string; + CI?: string; + } +} + +// Checking the environment variables simplifies the scripts in the package.json +// We use `cross-env BROWSER=true vitest` instead of `vitest --project "browser/*"` +// Which allows us to run `pnpm test:browser --project "x-charts"` for example. +const getWorkspaces = () => { + const getFill = () => { + const isBrowser = process.env.BROWSER === 'true'; + // We delete the env to prevent it from being used in the tests + delete process.env.BROWSER; + if (isBrowser) { + return 'browser'; + } + return 'jsdom'; + }; + + const fill = getFill(); + + return [ + `packages/*/vitest.config.${fill}.mts`, + ...(fill.includes('jsdom') ? [`docs/vitest.config.${fill}.mts`] : []), + ]; +}; + +export default defineConfig({ + test: { + workspace: getWorkspaces(), + coverage: { + provider: 'v8', + reporter: process.env.CI ? ['lcovonly'] : ['text'], + reportsDirectory: resolve(WORKSPACE_ROOT, 'coverage'), + include: ['packages/*/src/**/*.{ts,tsx}'], + exclude: ['**/*.{test,spec}.{js,ts,tsx}'], + }, + }, +}); diff --git a/vitest.shared.mts b/vitest.shared.mts new file mode 100644 index 0000000000000..7182870457568 --- /dev/null +++ b/vitest.shared.mts @@ -0,0 +1,84 @@ +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { defineConfig } from 'vitest/config'; + +const CURRENT_DIR = dirname(fileURLToPath(import.meta.url)); +const WORKSPACE_ROOT = resolve(CURRENT_DIR, './'); + +export default defineConfig({ + // We seem to need both this and the `env` property below to make it work. + define: { + 'process.env.NODE_ENV': '"test"', + LICENSE_DISABLE_CHECK: 'false', + }, + esbuild: { + minifyIdentifiers: false, + keepNames: true, + }, + resolve: { + alias: [ + // Generates resolver aliases for all packages and their plans. + ...[ + { lib: 'x-charts', plans: ['pro'] }, + { lib: 'x-date-pickers', plans: ['pro'] }, + { lib: 'x-tree-view', plans: ['pro'] }, + { lib: 'x-data-grid', plans: ['pro', 'premium', 'generator'] }, + { lib: 'x-internals' }, + { lib: 'x-license' }, + { lib: 'x-telemetry' }, + ].flatMap((v) => { + return [ + { + find: `@mui/${v.lib}`, + replacement: resolve(WORKSPACE_ROOT, `./packages/${v.lib}/src`), + }, + ...(v.plans ?? []).map((plan) => ({ + find: `@mui/${v.lib}-${plan}`, + replacement: resolve(WORKSPACE_ROOT, `./packages/${v.lib}-${plan}/src`), + })), + ]; + }), + { + find: 'test/utils', + replacement: new URL('./test/utils', import.meta.url).pathname, + }, + ], + }, + test: { + globals: true, + setupFiles: [new URL('test/setupVitest.ts', import.meta.url).pathname], + // Required for some tests that contain early returns or conditional tests. + passWithNoTests: true, + env: { + NODE_ENV: 'test', + }, + browser: { + isolate: false, + provider: 'playwright', + headless: true, + screenshotFailures: false, + }, + // Disable isolation to speed up the tests. + isolate: false, + // Performance improvements for the tests. + // https://vitest.dev/guide/improving-performance.html#improving-performance + ...(process.env.CI && { + // Important to avoid timeouts on CI. + fileParallelism: false, + // Increase the timeout for the tests due to slow CI machines. + testTimeout: 30000, + // Retry failed tests up to 3 times. This is useful for flaky tests. + retry: 3, + // Reduce the number of workers to avoid CI timeouts. + poolOptions: { + forks: { + singleFork: true, + }, + threads: { + singleThread: true, + }, + }, + }), + exclude: ['**/*.spec.{js,ts,tsx}', '**/node_modules/**', '**/dist/**'], + }, +});