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"