From 1eee24dc19612a20adfb29a8b51b78abf722ad44 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Wed, 6 Aug 2025 14:35:43 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F(docs-app)=20Switch=20from=20?= =?UTF-8?q?Jest=20tests=20to=20Vitest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have migrated the testing framework from Jest to Vitest for the Docs application. This change includes updates to test files, configuration files, and the addition of new setup files for Vitest. --- CHANGELOG.md | 1 + src/frontend/apps/impress/.env.test | 1 + src/frontend/apps/impress/jest.config.ts | 33 --- src/frontend/apps/impress/jest.setup.ts | 4 - .../apps/impress/jest/mocks/ComponentMock.js | 5 - .../apps/impress/jest/mocks/fileMock.js | 16 -- src/frontend/apps/impress/jest/mocks/svg.js | 3 - src/frontend/apps/impress/package.json | 11 +- .../src/api/__tests__/APIError.test.ts | 2 + .../impress/src/api/__tests__/config.test.ts | 2 + .../src/api/__tests__/fetchApi.test.tsx | 1 + .../src/api/__tests__/helpers.test.tsx | 5 +- .../impress/src/api/__tests__/utils.test.ts | 2 + .../src/components/__tests__/Box.spec.tsx | 1 + .../features/auth/__tests__/utils.test.tsx | 41 ++-- .../auth/hooks/__tests__/useAuth.test.tsx | 11 +- .../hook/__tests__/useSaveDoc.test.tsx | 71 +++--- .../doc-export/__tests__/ExportMIT.test.tsx | 9 +- .../doc-header/__tests__/DocToolBox.spec.tsx | 11 +- .../__tests__/ApiPlugin.test.tsx | 52 ++--- .../__tests__/OfflinePlugin.test.tsx | 20 +- .../__tests__/SyncManager.test.tsx | 29 ++- .../__tests__/useOffline.test.tsx | 8 +- .../__tests__/useSWRegister.test.tsx | 16 +- .../src/utils/__tests__/string.test.ts | 2 +- .../impress/src/utils/__tests__/url.test.tsx | 2 + src/frontend/apps/impress/vitest.config.ts | 25 ++ src/frontend/apps/impress/vitest.setup.ts | 4 + src/frontend/yarn.lock | 214 ++++++++++++------ 29 files changed, 325 insertions(+), 277 deletions(-) delete mode 100644 src/frontend/apps/impress/jest.config.ts delete mode 100644 src/frontend/apps/impress/jest.setup.ts delete mode 100644 src/frontend/apps/impress/jest/mocks/ComponentMock.js delete mode 100644 src/frontend/apps/impress/jest/mocks/fileMock.js delete mode 100644 src/frontend/apps/impress/jest/mocks/svg.js create mode 100644 src/frontend/apps/impress/vitest.config.ts create mode 100644 src/frontend/apps/impress/vitest.setup.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a15197d..69791c9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to ### Changed +- ♻️(docs-app) Switch from Jest tests to Vitest #1269 - ⚡️(frontend) improve accessibility: - #1248 - #1235 diff --git a/src/frontend/apps/impress/.env.test b/src/frontend/apps/impress/.env.test index 9a4d5142..4a30f18c 100644 --- a/src/frontend/apps/impress/.env.test +++ b/src/frontend/apps/impress/.env.test @@ -1 +1,2 @@ NEXT_PUBLIC_API_ORIGIN=http://test.jest +NEXT_PUBLIC_PUBLISH_AS_MIT=false diff --git a/src/frontend/apps/impress/jest.config.ts b/src/frontend/apps/impress/jest.config.ts deleted file mode 100644 index 05bfb0f3..00000000 --- a/src/frontend/apps/impress/jest.config.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Config } from 'jest'; -import nextJest from 'next/jest.js'; - -const createJestConfig = nextJest({ - dir: './', -}); - -// Add any custom config to be passed to Jest -const config: Config = { - coverageProvider: 'v8', - moduleNameMapper: { - '^@/docs/(.*)$': '/src/features/docs/$1', - '^@/(.*)$': '/src/$1', - }, - setupFilesAfterEnv: ['/jest.setup.ts'], - testEnvironment: 'jsdom', -}; - -const jestConfig = async () => { - const nextJestConfig = await createJestConfig(config)(); - return { - ...nextJestConfig, - moduleNameMapper: { - '\\.svg$': '/jest/mocks/svg.js', - '^.+\\.svg\\?url$': `/jest/mocks/fileMock.js`, - BlockNoteEditor: `/jest/mocks/ComponentMock.js`, - 'custom-blocks': `/jest/mocks/ComponentMock.js`, - ...nextJestConfig.moduleNameMapper, - }, - }; -}; - -export default jestConfig; diff --git a/src/frontend/apps/impress/jest.setup.ts b/src/frontend/apps/impress/jest.setup.ts deleted file mode 100644 index 564d8a6d..00000000 --- a/src/frontend/apps/impress/jest.setup.ts +++ /dev/null @@ -1,4 +0,0 @@ -import '@testing-library/jest-dom'; -import * as dotenv from 'dotenv'; - -dotenv.config({ path: './.env.test' }); diff --git a/src/frontend/apps/impress/jest/mocks/ComponentMock.js b/src/frontend/apps/impress/jest/mocks/ComponentMock.js deleted file mode 100644 index 812a08b1..00000000 --- a/src/frontend/apps/impress/jest/mocks/ComponentMock.js +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -export const ComponentMock = () => { - return
My component mocked
; -}; diff --git a/src/frontend/apps/impress/jest/mocks/fileMock.js b/src/frontend/apps/impress/jest/mocks/fileMock.js deleted file mode 100644 index 28a24984..00000000 --- a/src/frontend/apps/impress/jest/mocks/fileMock.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - src: '/img.jpg', - height: 40, - width: 40, - blurDataURL: 'data:image/png;base64,imagedata', -}; - -if ( - (typeof exports.default === 'function' || - (typeof exports.default === 'object' && exports.default !== null)) && - typeof exports.default.__esModule === 'undefined' -) { - Object.defineProperty(exports.default, '__esModule', { value: true }); - Object.assign(exports.default, exports); - module.exports = exports.default; -} diff --git a/src/frontend/apps/impress/jest/mocks/svg.js b/src/frontend/apps/impress/jest/mocks/svg.js deleted file mode 100644 index 0b7fc5b8..00000000 --- a/src/frontend/apps/impress/jest/mocks/svg.js +++ /dev/null @@ -1,3 +0,0 @@ -const nameMock = 'svg'; -export default nameMock; -export const ReactComponent = 'svg'; diff --git a/src/frontend/apps/impress/package.json b/src/frontend/apps/impress/package.json index b30b8085..f345e6a7 100644 --- a/src/frontend/apps/impress/package.json +++ b/src/frontend/apps/impress/package.json @@ -11,8 +11,8 @@ "lint": "tsc --noEmit && next lint", "prettier": "prettier --write .", "stylelint": "stylelint \"**/*.css\"", - "test": "jest", - "test:watch": "jest --watch" + "test": "vitest", + "test:watch": "vitest --watch" }, "dependencies": { "@ag-media/react-pdf-table": "2.0.3", @@ -67,24 +67,25 @@ "@testing-library/jest-dom": "6.6.4", "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", - "@types/jest": "30.0.0", "@types/lodash": "4.17.20", "@types/luxon": "3.7.1", "@types/node": "*", "@types/react": "*", "@types/react-dom": "*", + "@vitejs/plugin-react": "4.7.0", "cross-env": "10.0.0", "dotenv": "17.2.1", "eslint-config-impress": "*", "fetch-mock": "9.11.0", - "jest": "30.0.5", - "jest-environment-jsdom": "30.0.5", + "jsdom": "26.1.0", "node-fetch": "2.7.0", "prettier": "3.6.2", "stylelint": "16.23.0", "stylelint-config-standard": "39.0.0", "stylelint-prettier": "5.0.3", "typescript": "*", + "vite-tsconfig-paths": "5.1.4", + "vitest": "3.2.4", "webpack": "5.101.0", "workbox-webpack-plugin": "7.1.0" } diff --git a/src/frontend/apps/impress/src/api/__tests__/APIError.test.ts b/src/frontend/apps/impress/src/api/__tests__/APIError.test.ts index 395a5460..31b839e2 100644 --- a/src/frontend/apps/impress/src/api/__tests__/APIError.test.ts +++ b/src/frontend/apps/impress/src/api/__tests__/APIError.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { APIError, isAPIError } from '@/api'; describe('APIError', () => { diff --git a/src/frontend/apps/impress/src/api/__tests__/config.test.ts b/src/frontend/apps/impress/src/api/__tests__/config.test.ts index cb9bf268..a39ba539 100644 --- a/src/frontend/apps/impress/src/api/__tests__/config.test.ts +++ b/src/frontend/apps/impress/src/api/__tests__/config.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { baseApiUrl } from '@/api'; describe('config', () => { diff --git a/src/frontend/apps/impress/src/api/__tests__/fetchApi.test.tsx b/src/frontend/apps/impress/src/api/__tests__/fetchApi.test.tsx index f65b450f..2ac1dd1f 100644 --- a/src/frontend/apps/impress/src/api/__tests__/fetchApi.test.tsx +++ b/src/frontend/apps/impress/src/api/__tests__/fetchApi.test.tsx @@ -1,4 +1,5 @@ import fetchMock from 'fetch-mock'; +import { beforeEach, describe, expect, it } from 'vitest'; import { fetchAPI } from '@/api'; diff --git a/src/frontend/apps/impress/src/api/__tests__/helpers.test.tsx b/src/frontend/apps/impress/src/api/__tests__/helpers.test.tsx index e4706367..1368dd5f 100644 --- a/src/frontend/apps/impress/src/api/__tests__/helpers.test.tsx +++ b/src/frontend/apps/impress/src/api/__tests__/helpers.test.tsx @@ -1,5 +1,6 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { renderHook, waitFor } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; import { useAPIInfiniteQuery } from '@/api'; @@ -21,8 +22,8 @@ const createWrapper = () => { describe('helpers', () => { it('fetches and paginates correctly', async () => { - const mockAPI = jest - .fn, [{ page: number; query: string }]>() + const mockAPI = vi + .fn<(params: { page: number; query: string }) => Promise>() .mockResolvedValueOnce({ results: [{ id: 1 }], next: 'url?page=2', diff --git a/src/frontend/apps/impress/src/api/__tests__/utils.test.ts b/src/frontend/apps/impress/src/api/__tests__/utils.test.ts index 86433188..f4f61bad 100644 --- a/src/frontend/apps/impress/src/api/__tests__/utils.test.ts +++ b/src/frontend/apps/impress/src/api/__tests__/utils.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { errorCauses, getCSRFToken } from '@/api'; describe('utils', () => { diff --git a/src/frontend/apps/impress/src/components/__tests__/Box.spec.tsx b/src/frontend/apps/impress/src/components/__tests__/Box.spec.tsx index dc8ca857..26423ab4 100644 --- a/src/frontend/apps/impress/src/components/__tests__/Box.spec.tsx +++ b/src/frontend/apps/impress/src/components/__tests__/Box.spec.tsx @@ -1,3 +1,4 @@ +import '@testing-library/jest-dom'; import { render, screen } from '@testing-library/react'; import { Box } from '../Box'; diff --git a/src/frontend/apps/impress/src/features/auth/__tests__/utils.test.tsx b/src/frontend/apps/impress/src/features/auth/__tests__/utils.test.tsx index 3a9b28b3..1c9bff1b 100644 --- a/src/frontend/apps/impress/src/features/auth/__tests__/utils.test.tsx +++ b/src/frontend/apps/impress/src/features/auth/__tests__/utils.test.tsx @@ -1,42 +1,28 @@ -import { Crisp } from 'crisp-sdk-web'; import fetchMock from 'fetch-mock'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import { gotoLogout } from '../utils'; -jest.mock('crisp-sdk-web', () => ({ - ...jest.requireActual('crisp-sdk-web'), - Crisp: { - isCrispInjected: jest.fn().mockReturnValue(true), - setTokenId: jest.fn(), - user: { - setEmail: jest.fn(), - }, - session: { - reset: jest.fn(), - }, - }, +// Mock the Crisp service +vi.mock('@/services/Crisp', () => ({ + terminateCrispSession: vi.fn(), })); describe('utils', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); fetchMock.restore(); }); - it('checks support session is terminated when logout', () => { - jest.spyOn(console, 'error').mockImplementation(() => {}); + it('checks support session is terminated when logout', async () => { + const { terminateCrispSession } = await import('@/services/Crisp'); - window.$crisp = true; - const propertyDescriptors = Object.getOwnPropertyDescriptors(window); - for (const key in propertyDescriptors) { - propertyDescriptors[key].configurable = true; - } - const clonedWindow = Object.defineProperties({}, propertyDescriptors); - - Object.defineProperty(clonedWindow, 'location', { + // Mock window.location.replace + const mockReplace = vi.fn(); + Object.defineProperty(window, 'location', { value: { ...window.location, - replace: jest.fn(), + replace: mockReplace, }, writable: true, configurable: true, @@ -44,6 +30,9 @@ describe('utils', () => { gotoLogout(); - expect(Crisp.session.reset).toHaveBeenCalled(); + expect(terminateCrispSession).toHaveBeenCalled(); + expect(mockReplace).toHaveBeenCalledWith( + 'http://test.jest/api/v1.0/logout/', + ); }); }); diff --git a/src/frontend/apps/impress/src/features/auth/hooks/__tests__/useAuth.test.tsx b/src/frontend/apps/impress/src/features/auth/hooks/__tests__/useAuth.test.tsx index 56c56df3..809958de 100644 --- a/src/frontend/apps/impress/src/features/auth/hooks/__tests__/useAuth.test.tsx +++ b/src/frontend/apps/impress/src/features/auth/hooks/__tests__/useAuth.test.tsx @@ -1,13 +1,14 @@ import { renderHook, waitFor } from '@testing-library/react'; import fetchMock from 'fetch-mock'; import { Fragment } from 'react'; +import { beforeEach, describe, expect, vi } from 'vitest'; import { AbstractAnalytic } from '@/libs'; import { AppWrapper } from '@/tests/utils'; import { useAuth } from '../useAuth'; -const trackEventMock = jest.fn(); +const trackEventMock = vi.fn(); const flag = true; class TestAnalytic extends AbstractAnalytic { public constructor() { @@ -31,11 +32,11 @@ class TestAnalytic extends AbstractAnalytic { } } -jest.mock('next/router', () => ({ - ...jest.requireActual('next/router'), +vi.mock('next/router', async () => ({ + ...(await vi.importActual('next/router')), useRouter: () => ({ pathname: '/dashboard', - replace: jest.fn(), + replace: vi.fn(), }), })); @@ -43,7 +44,7 @@ const dummyUser = { id: '123', email: 'test@example.com' }; describe('useAuth hook - trackEvent effect', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); fetchMock.restore(); }); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useSaveDoc.test.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useSaveDoc.test.tsx index 28a59895..30f62d29 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useSaveDoc.test.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useSaveDoc.test.tsx @@ -1,36 +1,38 @@ import { act, renderHook, waitFor } from '@testing-library/react'; import fetchMock from 'fetch-mock'; import { useRouter } from 'next/router'; +import { Mock, beforeEach, describe, expect, it, vi } from 'vitest'; import * as Y from 'yjs'; import { AppWrapper } from '@/tests/utils'; import { useSaveDoc } from '../useSaveDoc'; -jest.mock('next/router', () => ({ - useRouter: jest.fn(), +vi.mock('next/router', () => ({ + useRouter: vi.fn(), })); -jest.mock('@/docs/doc-versioning', () => ({ +vi.mock('@/docs/doc-versioning', () => ({ KEY_LIST_DOC_VERSIONS: 'test-key-list-doc-versions', })); -jest.mock('@/docs/doc-management', () => ({ - useUpdateDoc: jest.requireActual('@/docs/doc-management/api/useUpdateDoc') - .useUpdateDoc, +vi.mock('@/docs/doc-management', async () => ({ + useUpdateDoc: ( + await vi.importActual('@/docs/doc-management/api/useUpdateDoc') + ).useUpdateDoc, })); describe('useSaveDoc', () => { const mockRouterEvents = { - on: jest.fn(), - off: jest.fn(), + on: vi.fn(), + off: vi.fn(), }; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); fetchMock.restore(); - (useRouter as jest.Mock).mockReturnValue({ + (useRouter as Mock).mockReturnValue({ events: mockRouterEvents, }); }); @@ -39,7 +41,7 @@ describe('useSaveDoc', () => { const yDoc = new Y.Doc(); const docId = 'test-doc-id'; - const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + const addEventListenerSpy = vi.spyOn(window, 'addEventListener'); renderHook(() => useSaveDoc(docId, yDoc, true, true), { wrapper: AppWrapper, @@ -60,8 +62,8 @@ describe('useSaveDoc', () => { addEventListenerSpy.mockRestore(); }); - it('should not save when canSave is false', async () => { - jest.useFakeTimers(); + it('should not save when canSave is false', () => { + vi.useFakeTimers(); const yDoc = new Y.Doc(); const docId = 'test-doc-id'; @@ -80,22 +82,19 @@ describe('useSaveDoc', () => { act(() => { // Trigger a local update yDoc.getMap('test').set('key', 'value'); + + // Advance timers to trigger the save interval + vi.advanceTimersByTime(61000); }); - act(() => { - // Now advance timers after state has updated - jest.advanceTimersByTime(61000); - }); + // Since canSave is false, no API call should be made + expect(fetchMock.calls().length).toBe(0); - await waitFor(() => { - expect(fetchMock.calls().length).toBe(0); - }); - - jest.useRealTimers(); + vi.useRealTimers(); }); it('should save when there are local changes', async () => { - jest.useFakeTimers(); + vi.useFakeTimers(); const yDoc = new Y.Doc(); const docId = 'test-doc-id'; @@ -117,21 +116,22 @@ describe('useSaveDoc', () => { }); act(() => { - // Now advance timers after state has updated - jest.advanceTimersByTime(61000); + // Advance timers to trigger the save interval + vi.advanceTimersByTime(61000); }); + // Switch to real timers to allow the mutation promise to resolve + vi.useRealTimers(); + await waitFor(() => { expect(fetchMock.lastCall()?.[0]).toBe( 'http://test.jest/api/v1.0/documents/test-doc-id/', ); }); - - jest.useRealTimers(); }); - it('should not save when there are no local changes', async () => { - jest.useFakeTimers(); + it('should not save when there are no local changes', () => { + vi.useFakeTimers(); const yDoc = new Y.Doc(); const docId = 'test-doc-id'; @@ -148,21 +148,20 @@ describe('useSaveDoc', () => { }); act(() => { - // Now advance timers after state has updated - jest.advanceTimersByTime(61000); + // Advance timers without triggering any local updates + vi.advanceTimersByTime(61000); }); - await waitFor(() => { - expect(fetchMock.calls().length).toBe(0); - }); + // Since there are no local changes, no API call should be made + expect(fetchMock.calls().length).toBe(0); - jest.useRealTimers(); + vi.useRealTimers(); }); it('should cleanup event listeners on unmount', () => { const yDoc = new Y.Doc(); const docId = 'test-doc-id'; - const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); + const removeEventListenerSpy = vi.spyOn(window, 'removeEventListener'); const { unmount } = renderHook(() => useSaveDoc(docId, yDoc, true, true), { wrapper: AppWrapper, diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/__tests__/ExportMIT.test.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/__tests__/ExportMIT.test.tsx index a4c7e740..398f5213 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/__tests__/ExportMIT.test.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-export/__tests__/ExportMIT.test.tsx @@ -1,9 +1,10 @@ +import { afterAll, afterEach, describe, expect, it, vi } from 'vitest'; const originalEnv = process.env.NEXT_PUBLIC_PUBLISH_AS_MIT; -jest.mock('@/features/docs/doc-export/utils', () => ({ +vi.mock('@/features/docs/doc-export/utils', () => ({ anything: true, })); -jest.mock('@/features/docs/doc-export/components/ModalExport', () => ({ +vi.mock('@/features/docs/doc-export/components/ModalExport', () => ({ ModalExport: () => ModalExport, })); @@ -13,8 +14,8 @@ describe('useModuleExport', () => { }); afterEach(() => { - jest.clearAllMocks(); - jest.resetModules(); + vi.clearAllMocks(); + vi.resetModules(); }); it('should return undefined when NEXT_PUBLIC_PUBLISH_AS_MIT is true', async () => { diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx index 11856db5..f6245ab5 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx @@ -1,6 +1,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React, { Fragment } from 'react'; +import { beforeEach, describe, expect, vi } from 'vitest'; import { AbstractAnalytic, Analytics } from '@/libs'; import { AppWrapper } from '@/tests/utils'; @@ -28,14 +29,10 @@ class TestAnalytic extends AbstractAnalytic { } } -jest.mock('@/features/docs/doc-export/', () => ({ - ModalExport: () => ModalExport, -})); - -jest.mock('next/router', () => ({ - ...jest.requireActual('next/router'), +vi.mock('next/router', async () => ({ + ...(await vi.importActual('next/router')), useRouter: () => ({ - push: jest.fn(), + push: vi.fn(), }), })); diff --git a/src/frontend/apps/impress/src/features/service-worker/__tests__/ApiPlugin.test.tsx b/src/frontend/apps/impress/src/features/service-worker/__tests__/ApiPlugin.test.tsx index 198fc91f..bd6224fd 100644 --- a/src/frontend/apps/impress/src/features/service-worker/__tests__/ApiPlugin.test.tsx +++ b/src/frontend/apps/impress/src/features/service-worker/__tests__/ApiPlugin.test.tsx @@ -1,34 +1,30 @@ -/** - * @jest-environment node - */ - -import '@testing-library/jest-dom'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import { RequestSerializer } from '../RequestSerializer'; import { ApiPlugin } from '../plugins/ApiPlugin'; -const mockedGet = jest.fn().mockResolvedValue({}); -const mockedGetAllKeys = jest.fn().mockResolvedValue([]); -const mockedPut = jest.fn().mockResolvedValue({}); -const mockedDelete = jest.fn().mockResolvedValue({}); -const mockedClose = jest.fn().mockResolvedValue({}); -const mockedOpendDB = jest.fn().mockResolvedValue({ +const mockedGet = vi.fn().mockResolvedValue({}); +const mockedGetAllKeys = vi.fn().mockResolvedValue([]); +const mockedPut = vi.fn().mockResolvedValue({}); +const mockedDelete = vi.fn().mockResolvedValue({}); +const mockedClose = vi.fn().mockResolvedValue({}); +const mockedOpendDB = vi.fn().mockResolvedValue({ get: mockedGet, getAllKeys: mockedGetAllKeys, - getAll: jest.fn().mockResolvedValue([]), + getAll: vi.fn().mockResolvedValue([]), put: mockedPut, delete: mockedDelete, - clear: jest.fn().mockResolvedValue({}), + clear: vi.fn().mockResolvedValue({}), close: mockedClose, }); -jest.mock('idb', () => ({ - ...jest.requireActual('idb'), +vi.mock('idb', async () => ({ + ...(await vi.importActual('idb')), openDB: () => mockedOpendDB(), })); describe('ApiPlugin', () => { - afterEach(() => jest.clearAllMocks()); + afterEach(() => vi.clearAllMocks()); [ { type: 'item', table: 'doc-item' }, @@ -36,7 +32,7 @@ describe('ApiPlugin', () => { { type: 'update', table: 'doc-item' }, ].forEach(({ type, table }) => { it(`calls fetchDidSucceed with type ${type} and status 200`, async () => { - const mockedSync = jest.fn().mockResolvedValue({}); + const mockedSync = vi.fn().mockResolvedValue({}); const apiPlugin = new ApiPlugin({ tableName: table as any, type: type as any, @@ -55,7 +51,7 @@ describe('ApiPlugin', () => { json: () => body, } as unknown as Request, } as any; - const mockedClone = jest.fn().mockReturnValue(requestInit.request); + const mockedClone = vi.fn().mockReturnValue(requestInit.request); await apiPlugin.requestWillFetch?.(requestInit); const response = await apiPlugin.fetchDidSucceed?.({ @@ -81,7 +77,7 @@ describe('ApiPlugin', () => { const apiPlugin = new ApiPlugin({ tableName: table as any, type: type as any, - syncManager: jest.fn() as any, + syncManager: vi.fn() as any, }); const body = { lastName: 'Doe' }; @@ -114,7 +110,7 @@ describe('ApiPlugin', () => { { type: 'item', withClone: false }, ].forEach(({ type, withClone }) => { it(`calls requestWillFetch with type ${type}`, async () => { - const mockedSync = jest.fn().mockResolvedValue({}); + const mockedSync = vi.fn().mockResolvedValue({}); const apiPlugin = new ApiPlugin({ type: 'update', @@ -123,7 +119,7 @@ describe('ApiPlugin', () => { } as any, }); - const mockedClone = jest.fn().mockResolvedValue({}); + const mockedClone = vi.fn().mockResolvedValue({}); const requestInit = { request: { url: 'test-url', @@ -189,9 +185,9 @@ describe('ApiPlugin', () => { } as unknown as Request, } as any; - const mockedClone = jest.fn().mockReturnValue(requestInit.request); + const mockedClone = vi.fn().mockReturnValue(requestInit.request); - const mockedSync = jest.fn().mockResolvedValue({}); + const mockedSync = vi.fn().mockResolvedValue({}); const apiPlugin = new ApiPlugin({ type: 'update', syncManager: { @@ -265,9 +261,9 @@ describe('ApiPlugin', () => { } as unknown as Request, } as any; - const mockedClone = jest.fn().mockReturnValue(requestInit.request); + const mockedClone = vi.fn().mockReturnValue(requestInit.request); - const mockedSync = jest.fn().mockResolvedValue({}); + const mockedSync = vi.fn().mockResolvedValue({}); const apiPlugin = new ApiPlugin({ type: 'delete', syncManager: { @@ -334,7 +330,7 @@ describe('ApiPlugin', () => { Object.defineProperty(global, 'self', { value: { crypto: { - randomUUID: jest.fn().mockReturnValue('444555'), + randomUUID: vi.fn().mockReturnValue('444555'), }, }, }); @@ -351,9 +347,9 @@ describe('ApiPlugin', () => { } as unknown as Request, } as any; - const mockedClone = jest.fn().mockReturnValue(requestInit.request); + const mockedClone = vi.fn().mockReturnValue(requestInit.request); - const mockedSync = jest.fn().mockResolvedValue({}); + const mockedSync = vi.fn().mockResolvedValue({}); const apiPlugin = new ApiPlugin({ type: 'create', syncManager: { diff --git a/src/frontend/apps/impress/src/features/service-worker/__tests__/OfflinePlugin.test.tsx b/src/frontend/apps/impress/src/features/service-worker/__tests__/OfflinePlugin.test.tsx index 183634e5..65f96eca 100644 --- a/src/frontend/apps/impress/src/features/service-worker/__tests__/OfflinePlugin.test.tsx +++ b/src/frontend/apps/impress/src/features/service-worker/__tests__/OfflinePlugin.test.tsx @@ -1,15 +1,14 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; /** - * @jest-environment node + * @vitest-environment node */ -import '@testing-library/jest-dom'; - import { MESSAGE_TYPE } from '../conf'; import { OfflinePlugin } from '../plugins/OfflinePlugin'; const mockServiceWorkerScope = { clients: { - matchAll: jest.fn().mockResolvedValue([]), + matchAll: vi.fn().mockResolvedValue([]), }, } as unknown as ServiceWorkerGlobalScope; @@ -19,11 +18,11 @@ const mockServiceWorkerScope = { } as unknown as ServiceWorkerGlobalScope; describe('OfflinePlugin', () => { - afterEach(() => jest.clearAllMocks()); + afterEach(() => vi.clearAllMocks()); it(`calls fetchDidSucceed`, async () => { const apiPlugin = new OfflinePlugin(); - const postMessageSpy = jest.spyOn(apiPlugin, 'postMessage'); + const postMessageSpy = vi.spyOn(apiPlugin, 'postMessage'); await apiPlugin.fetchDidSucceed?.({ response: new Response(), @@ -34,7 +33,7 @@ describe('OfflinePlugin', () => { it(`calls fetchDidFail`, async () => { const apiPlugin = new OfflinePlugin(); - const postMessageSpy = jest.spyOn(apiPlugin, 'postMessage'); + const postMessageSpy = vi.spyOn(apiPlugin, 'postMessage'); await apiPlugin.fetchDidFail?.({} as any); @@ -43,12 +42,9 @@ describe('OfflinePlugin', () => { it(`calls postMessage`, async () => { const apiPlugin = new OfflinePlugin(); - const mockClients = [ - { postMessage: jest.fn() }, - { postMessage: jest.fn() }, - ]; + const mockClients = [{ postMessage: vi.fn() }, { postMessage: vi.fn() }]; - mockServiceWorkerScope.clients.matchAll = jest + mockServiceWorkerScope.clients.matchAll = vi .fn() .mockResolvedValue(mockClients); diff --git a/src/frontend/apps/impress/src/features/service-worker/__tests__/SyncManager.test.tsx b/src/frontend/apps/impress/src/features/service-worker/__tests__/SyncManager.test.tsx index 95c2c082..bc381fc5 100644 --- a/src/frontend/apps/impress/src/features/service-worker/__tests__/SyncManager.test.tsx +++ b/src/frontend/apps/impress/src/features/service-worker/__tests__/SyncManager.test.tsx @@ -1,24 +1,23 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; /** * @jest-environment node */ -import '@testing-library/jest-dom'; - import { SyncManager } from '../SyncManager'; -const mockedSleep = jest.fn(); -jest.mock('@/utils/system', () => ({ - sleep: jest.fn().mockImplementation((ms) => mockedSleep(ms)), +const mockedSleep = vi.fn(); +vi.mock('@/utils/system', () => ({ + sleep: vi.fn().mockImplementation((ms) => mockedSleep(ms)), })); const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); describe('SyncManager', () => { - afterEach(() => jest.clearAllMocks()); + afterEach(() => vi.clearAllMocks()); it('checks SyncManager no sync to do', async () => { - const toSync = jest.fn(); - const hasSyncToDo = jest.fn().mockResolvedValue(false); + const toSync = vi.fn(); + const hasSyncToDo = vi.fn().mockResolvedValue(false); new SyncManager(toSync, hasSyncToDo); await delay(100); @@ -28,8 +27,8 @@ describe('SyncManager', () => { }); it('checks SyncManager sync to do', async () => { - const toSync = jest.fn(); - const hasSyncToDo = jest.fn().mockResolvedValue(true); + const toSync = vi.fn(); + const hasSyncToDo = vi.fn().mockResolvedValue(true); new SyncManager(toSync, hasSyncToDo); await delay(100); @@ -39,10 +38,10 @@ describe('SyncManager', () => { }); it('checks SyncManager sync to do trigger error', async () => { - jest.spyOn(console, 'error').mockImplementation(() => {}); + vi.spyOn(console, 'error').mockImplementation(() => {}); - const toSync = jest.fn().mockRejectedValue(new Error('error')); - const hasSyncToDo = jest.fn().mockResolvedValue(true); + const toSync = vi.fn().mockRejectedValue(new Error('error')); + const hasSyncToDo = vi.fn().mockResolvedValue(true); new SyncManager(toSync, hasSyncToDo); await delay(100); @@ -56,8 +55,8 @@ describe('SyncManager', () => { }); it('checks SyncManager multiple sync to do', async () => { - const toSync = jest.fn().mockReturnValue(delay(200)); - const hasSyncToDo = jest.fn().mockResolvedValue(true); + const toSync = vi.fn().mockReturnValue(delay(200)); + const hasSyncToDo = vi.fn().mockResolvedValue(true); const syncManager = new SyncManager(toSync, hasSyncToDo); await syncManager.sync(); diff --git a/src/frontend/apps/impress/src/features/service-worker/__tests__/useOffline.test.tsx b/src/frontend/apps/impress/src/features/service-worker/__tests__/useOffline.test.tsx index a2920ce2..1e2f7b76 100644 --- a/src/frontend/apps/impress/src/features/service-worker/__tests__/useOffline.test.tsx +++ b/src/frontend/apps/impress/src/features/service-worker/__tests__/useOffline.test.tsx @@ -1,11 +1,11 @@ -import '@testing-library/jest-dom'; import { act, renderHook } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { MESSAGE_TYPE } from '../conf'; import { useIsOffline, useOffline } from '../hooks/useOffline'; -const mockAddEventListener = jest.fn(); -const mockRemoveEventListener = jest.fn(); +const mockAddEventListener = vi.fn(); +const mockRemoveEventListener = vi.fn(); Object.defineProperty(navigator, 'serviceWorker', { value: { addEventListener: mockAddEventListener, @@ -16,7 +16,7 @@ Object.defineProperty(navigator, 'serviceWorker', { describe('useOffline', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('should set isOffline to true when receiving an offline message', () => { diff --git a/src/frontend/apps/impress/src/features/service-worker/__tests__/useSWRegister.test.tsx b/src/frontend/apps/impress/src/features/service-worker/__tests__/useSWRegister.test.tsx index cf168d8d..cb01219d 100644 --- a/src/frontend/apps/impress/src/features/service-worker/__tests__/useSWRegister.test.tsx +++ b/src/frontend/apps/impress/src/features/service-worker/__tests__/useSWRegister.test.tsx @@ -1,5 +1,5 @@ -import '@testing-library/jest-dom'; import { render } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; import { useSWRegister } from '../hooks/useSWRegister'; @@ -12,9 +12,9 @@ const TestComponent = () => { describe('useSWRegister', () => { it('checks service-worker is register', () => { process.env.NEXT_PUBLIC_BUILD_ID = '123456'; - jest.spyOn(console, 'error').mockImplementation(() => {}); + vi.spyOn(console, 'error').mockImplementation(() => {}); - const registerSpy = jest.fn(); + const registerSpy = vi.fn(); registerSpy.mockImplementation( () => new Promise((reject) => { @@ -22,11 +22,13 @@ describe('useSWRegister', () => { }), ); + const addEventListenerSpy = vi.fn(); + Object.defineProperty(navigator, 'serviceWorker', { value: { register: registerSpy, - addEventListener: jest.fn(), - removeEventListener: jest.fn(), + addEventListener: addEventListenerSpy, + removeEventListener: vi.fn(), }, writable: true, }); @@ -34,7 +36,7 @@ describe('useSWRegister', () => { render(); expect(registerSpy).toHaveBeenCalledWith('/service-worker.js?v=123456'); - expect(navigator.serviceWorker.addEventListener).toHaveBeenCalledWith( + expect(addEventListenerSpy).toHaveBeenCalledWith( 'controllerchange', expect.any(Function), ); @@ -44,7 +46,7 @@ describe('useSWRegister', () => { process.env.NEXT_PUBLIC_SW_DEACTIVATED = 'true'; process.env.NEXT_PUBLIC_BUILD_ID = '123456'; - const registerSpy = jest.fn(); + const registerSpy = vi.fn(); registerSpy.mockImplementation( () => new Promise((reject) => { diff --git a/src/frontend/apps/impress/src/utils/__tests__/string.test.ts b/src/frontend/apps/impress/src/utils/__tests__/string.test.ts index c8ea85d3..32111954 100644 --- a/src/frontend/apps/impress/src/utils/__tests__/string.test.ts +++ b/src/frontend/apps/impress/src/utils/__tests__/string.test.ts @@ -1,4 +1,4 @@ -import '@testing-library/jest-dom'; +import { describe, expect, it } from 'vitest'; import { isValidEmail } from '../string'; diff --git a/src/frontend/apps/impress/src/utils/__tests__/url.test.tsx b/src/frontend/apps/impress/src/utils/__tests__/url.test.tsx index 26e33fc7..a149ceb2 100644 --- a/src/frontend/apps/impress/src/utils/__tests__/url.test.tsx +++ b/src/frontend/apps/impress/src/utils/__tests__/url.test.tsx @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { isSafeUrl } from '@/utils/url'; describe('isSafeUrl', () => { diff --git a/src/frontend/apps/impress/vitest.config.ts b/src/frontend/apps/impress/vitest.config.ts new file mode 100644 index 00000000..bfd70093 --- /dev/null +++ b/src/frontend/apps/impress/vitest.config.ts @@ -0,0 +1,25 @@ +/// +import react from '@vitejs/plugin-react'; +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + react(), + tsconfigPaths({ + root: '.', + projects: ['./tsconfig.json'], + }), + ], + test: { + globals: true, + environment: 'jsdom', + setupFiles: './vitest.setup.ts', + coverage: { + provider: 'v8', + }, + }, + define: { + 'process.env.NODE_ENV': 'test', + }, +}); diff --git a/src/frontend/apps/impress/vitest.setup.ts b/src/frontend/apps/impress/vitest.setup.ts new file mode 100644 index 00000000..de0945cf --- /dev/null +++ b/src/frontend/apps/impress/vitest.setup.ts @@ -0,0 +1,4 @@ +import '@testing-library/jest-dom/vitest'; +import * as dotenv from 'dotenv'; + +dotenv.config({ path: './.env.test', quiet: true }); diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock index 82d9ed4b..e434ccf9 100644 --- a/src/frontend/yarn.lock +++ b/src/frontend/yarn.lock @@ -75,6 +75,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.28.0": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.3.tgz#aceddde69c5d1def69b839d09efa3e3ff59c97cb" + integrity sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.28.3" + "@babel/helpers" "^7.28.3" + "@babel/parser" "^7.28.3" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.3" + "@babel/types" "^7.28.2" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/generator@^7.27.5", "@babel/generator@^7.28.0": version "7.28.0" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.0.tgz#9cc2f7bd6eb054d77dc66c2664148a0c5118acd2" @@ -86,6 +107,17 @@ "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" +"@babel/generator@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" + integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== + dependencies: + "@babel/parser" "^7.28.3" + "@babel/types" "^7.28.2" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3": version "7.27.3" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" @@ -167,6 +199,15 @@ "@babel/helper-validator-identifier" "^7.27.1" "@babel/traverse" "^7.27.3" +"@babel/helper-module-transforms@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" + integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.28.3" + "@babel/helper-optimise-call-expression@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz#c65221b61a643f3e62705e5dd2b5f115e35f9200" @@ -237,6 +278,14 @@ "@babel/template" "^7.27.2" "@babel/types" "^7.28.2" +"@babel/helpers@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.3.tgz#b83156c0a2232c133d1b535dd5d3452119c7e441" + integrity sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.2" + "@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.2", "@babel/parser@^7.28.0": version "7.28.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.0.tgz#979829fbab51a29e13901e5a80713dbcb840825e" @@ -244,6 +293,13 @@ dependencies: "@babel/types" "^7.28.0" +"@babel/parser@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.3.tgz#d2d25b814621bca5fe9d172bc93792547e7a2a71" + integrity sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA== + dependencies: + "@babel/types" "^7.28.2" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz#61dd8a8e61f7eb568268d1b5f129da3eee364bf9" @@ -751,6 +807,20 @@ dependencies: "@babel/plugin-transform-react-jsx" "^7.27.1" +"@babel/plugin-transform-react-jsx-self@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz#af678d8506acf52c577cac73ff7fe6615c85fc92" + integrity sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-jsx-source@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz#dcfe2c24094bb757bf73960374e7c55e434f19f0" + integrity sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-transform-react-jsx@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz#1023bc94b78b0a2d68c82b5e96aed573bcfb9db0" @@ -1005,6 +1075,19 @@ "@babel/types" "^7.28.0" debug "^4.3.1" +"@babel/traverse@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.3.tgz#6911a10795d2cce43ec6a28cffc440cca2593434" + integrity sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.3" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.2" + debug "^4.3.1" + "@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.21.3", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.0", "@babel/types@^7.28.2", "@babel/types@^7.4.4": version "7.28.2" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" @@ -1969,19 +2052,6 @@ resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== -"@jest/environment-jsdom-abstract@30.0.5": - version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.0.5.tgz#7299cca59b3e84547ca3d1bbd4e7d36b4b44d426" - integrity sha512-gpWwiVxZunkoglP8DCnT3As9x5O8H6gveAOpvaJd2ATAoSh7ZSSCWbr9LQtUMvr8WD3VjG9YnDhsmkCK5WN1rQ== - dependencies: - "@jest/environment" "30.0.5" - "@jest/fake-timers" "30.0.5" - "@jest/types" "30.0.5" - "@types/jsdom" "^21.1.7" - "@types/node" "*" - jest-mock "30.0.5" - jest-util "30.0.5" - "@jest/environment@30.0.5": version "30.0.5" resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.0.5.tgz#eaaae0403c7d3f8414053c2224acc3011e1c3a1b" @@ -4300,6 +4370,11 @@ resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f" integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg== +"@rolldown/pluginutils@1.0.0-beta.27": + version "1.0.0-beta.27" + resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz#47d2bf4cef6d470b22f5831b420f8964e0bf755f" + integrity sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA== + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -5346,15 +5421,6 @@ expect "^30.0.0" pretty-format "^30.0.0" -"@types/jsdom@^21.1.7": - version "21.1.7" - resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-21.1.7.tgz#9edcb09e0b07ce876e7833922d3274149c898cfa" - integrity sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA== - dependencies: - "@types/node" "*" - "@types/tough-cookie" "*" - parse5 "^7.0.0" - "@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -5577,11 +5643,6 @@ dependencies: "@types/node" "*" -"@types/tough-cookie@*": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" - integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== - "@types/trusted-types@^2.0.2": version "2.0.7" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" @@ -5816,6 +5877,18 @@ resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz#538b1e103bf8d9864e7b85cc96fa8d6fb6c40777" integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g== +"@vitejs/plugin-react@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz#647af4e7bb75ad3add578e762ad984b90f4a24b9" + integrity sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA== + dependencies: + "@babel/core" "^7.28.0" + "@babel/plugin-transform-react-jsx-self" "^7.27.1" + "@babel/plugin-transform-react-jsx-source" "^7.27.1" + "@rolldown/pluginutils" "1.0.0-beta.27" + "@types/babel__core" "^7.20.5" + react-refresh "^0.17.0" + "@vitest/expect@3.2.4": version "3.2.4" resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-3.2.4.tgz#8362124cd811a5ee11c5768207b9df53d34f2433" @@ -8734,6 +8807,11 @@ globjoin@^0.1.4: resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg== +globrex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" + integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== + gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" @@ -9788,17 +9866,6 @@ jest-each@30.0.5: jest-util "30.0.5" pretty-format "30.0.5" -jest-environment-jsdom@30.0.5: - version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-30.0.5.tgz#36351cc8a14fcd54945da0beb029af493d7d5764" - integrity sha512-BmnDEoAH+jEjkPrvE9DTKS2r3jYSJWlN/r46h0/DBUxKrkgt2jAZ5Nj4wXLAcV1KWkRpcFqA5zri9SWzJZ1cCg== - dependencies: - "@jest/environment" "30.0.5" - "@jest/environment-jsdom-abstract" "30.0.5" - "@types/jsdom" "^21.1.7" - "@types/node" "*" - jsdom "^26.1.0" - jest-environment-node@30.0.5: version "30.0.5" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-30.0.5.tgz#6a98dd80e0384ead67ed05643381395f6cda93c9" @@ -10080,6 +10147,32 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsdom@26.1.0: + version "26.1.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-26.1.0.tgz#ab5f1c1cafc04bd878725490974ea5e8bf0c72b3" + integrity sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg== + dependencies: + cssstyle "^4.2.1" + data-urls "^5.0.0" + decimal.js "^10.5.0" + html-encoding-sniffer "^4.0.0" + http-proxy-agent "^7.0.2" + https-proxy-agent "^7.0.6" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.16" + parse5 "^7.2.1" + rrweb-cssom "^0.8.0" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^5.1.1" + w3c-xmlserializer "^5.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^3.1.1" + whatwg-mimetype "^4.0.0" + whatwg-url "^14.1.1" + ws "^8.18.0" + xml-name-validator "^5.0.0" + jsdom@^25.0.1: version "25.0.1" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-25.0.1.tgz#536ec685c288fc8a5773a65f82d8b44badcc73ef" @@ -10107,32 +10200,6 @@ jsdom@^25.0.1: ws "^8.18.0" xml-name-validator "^5.0.0" -jsdom@^26.1.0: - version "26.1.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-26.1.0.tgz#ab5f1c1cafc04bd878725490974ea5e8bf0c72b3" - integrity sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg== - dependencies: - cssstyle "^4.2.1" - data-urls "^5.0.0" - decimal.js "^10.5.0" - html-encoding-sniffer "^4.0.0" - http-proxy-agent "^7.0.2" - https-proxy-agent "^7.0.6" - is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.16" - parse5 "^7.2.1" - rrweb-cssom "^0.8.0" - saxes "^6.0.0" - symbol-tree "^3.2.4" - tough-cookie "^5.1.1" - w3c-xmlserializer "^5.0.0" - webidl-conversions "^7.0.0" - whatwg-encoding "^3.1.1" - whatwg-mimetype "^4.0.0" - whatwg-url "^14.1.1" - ws "^8.18.0" - xml-name-validator "^5.0.0" - jsesc@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" @@ -12284,6 +12351,11 @@ react-number-format@^5.4.3: resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-5.4.4.tgz#d31f0e260609431500c8d3f81bbd3ae1fb7cacad" integrity sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA== +react-refresh@^0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.17.0.tgz#b7e579c3657f23d04eccbe4ad2e58a8ed51e7e53" + integrity sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ== + react-remove-scroll-bar@^2.3.7: version "2.3.8" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz#99c20f908ee467b385b68a3469b4a3e750012223" @@ -13952,6 +14024,11 @@ tsc-alias@1.8.16: normalize-path "^3.0.0" plimit-lit "^1.2.6" +tsconfck@^3.0.3: + version "3.1.6" + resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.6.tgz#da1f0b10d82237ac23422374b3fce1edb23c3ead" + integrity sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w== + tsconfig-paths@^3.15.0: version "3.15.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" @@ -14468,6 +14545,15 @@ vite-node@3.2.4: pathe "^2.0.3" vite "^5.0.0 || ^6.0.0 || ^7.0.0-0" +vite-tsconfig-paths@5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz#d9a71106a7ff2c1c840c6f1708042f76a9212ed4" + integrity sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w== + dependencies: + debug "^4.1.1" + globrex "^0.1.2" + tsconfck "^3.0.3" + "vite@^5.0.0 || ^6.0.0 || ^7.0.0-0": version "7.1.0" resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.0.tgz#6fb13c74c13cfdd0e200ee61d6ea6e8fafc2e8b5"