diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a63dd5afd..5aa9d4e998 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,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 9a4d51422f..4a30f18c6e 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 05bfb0f36a..0000000000 --- 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 564d8a6d26..0000000000 --- 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 812a08b1ca..0000000000 --- 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 28a2498450..0000000000 --- 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 0b7fc5b8b7..0000000000 --- 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 d30d9c5ed6..3981fa972e 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.3", "@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.6.2", "@types/node": "*", "@types/react": "*", "@types/react-dom": "*", + "@vitejs/plugin-react": "4.7.0", "cross-env": "7.0.3", "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.22.0", "stylelint-config-standard": "38.0.0", "stylelint-prettier": "5.0.3", "typescript": "*", + "vite-tsconfig-paths": "5.1.4", + "vitest": "3.2.4", "webpack": "5.100.2", "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 395a546056..31b839e265 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 cb9bf26801..a39ba539ee 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 f65b450f95..2ac1dd1f58 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 e47063679a..1368dd5f0a 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 864331889f..f4f61bad8b 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 dc8ca857e1..26423ab40d 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 3a9b28b351..1c9bff1bf3 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 56c56df31d..809958de94 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 28a5989572..30f62d2913 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'); - }); - act(() => { - // Now advance timers after state has updated - jest.advanceTimersByTime(61000); + // Advance timers to trigger the save interval + vi.advanceTimersByTime(61000); }); - await waitFor(() => { - expect(fetchMock.calls().length).toBe(0); - }); + // Since canSave is false, no API call should be made + 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 a4c7e74099..8c3efaa027 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,20 +1,14 @@ +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', () => ({ - anything: true, -})); -jest.mock('@/features/docs/doc-export/components/ModalExport', () => ({ - ModalExport: () => ModalExport, -})); - describe('useModuleExport', () => { afterAll(() => { process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = originalEnv; }); afterEach(() => { - jest.clearAllMocks(); - jest.resetModules(); + vi.clearAllMocks(); + vi.resetModules(); }); it('should return undefined when NEXT_PUBLIC_PUBLISH_AS_MIT is true', async () => { @@ -22,7 +16,7 @@ describe('useModuleExport', () => { const Export = await import('@/features/docs/doc-export/'); expect(Export.default).toBeUndefined(); - }); + }, 10000); it('should load modules when NEXT_PUBLIC_PUBLISH_AS_MIT is false', async () => { process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'false'; 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 11856db563..f6245ab5c5 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/docs/doc-header/__tests__/DocToolBoxLicence.spec.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxLicence.spec.tsx new file mode 100644 index 0000000000..6ef8d29208 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxLicence.spec.tsx @@ -0,0 +1,69 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { afterAll, beforeEach, describe, expect, vi } from 'vitest'; + +import { AppWrapper } from '@/tests/utils'; + +const originalEnv = process.env.NEXT_PUBLIC_PUBLISH_AS_MIT; + +vi.mock('next/router', async () => ({ + ...(await vi.importActual('next/router')), + useRouter: () => ({ + push: vi.fn(), + }), +})); + +const doc = { + nb_accesses: 1, + abilities: { + versions_list: true, + destroy: true, + }, +}; + +describe('DocToolBox - Licence', () => { + afterAll(() => { + process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = originalEnv; + }); + + beforeEach(() => { + vi.clearAllMocks(); + vi.resetModules(); + }); + + test('The export button is rendered when MIT version is deactivated', async () => { + process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'false'; + + const { DocToolBox } = await import('../components/DocToolBox'); + + render(, { + wrapper: AppWrapper, + }); + const optionsButton = await screen.findByLabelText('Export the document'); + await userEvent.click(optionsButton); + expect( + await screen.findByText( + 'Download your document in a .docx or .pdf format.', + ), + ).toBeInTheDocument(); + }, 10000); + + test('The export button is not rendered when MIT version is activated', async () => { + process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'true'; + + const { DocToolBox } = await import('../components/DocToolBox'); + + render(, { + wrapper: AppWrapper, + }); + + expect( + screen.getByLabelText('Open the document options'), + ).toBeInTheDocument(); + + expect( + screen.queryByLabelText('Export the document'), + ).not.toBeInTheDocument(); + }); +}); diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx index b84321c17d..f0c1192864 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx @@ -241,6 +241,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => { setIsModalExportOpen(true); }} size={isSmallMobile ? 'small' : 'medium'} + aria-label={t('Export the document')} /> )} 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 198fc91ff5..bd6224fdcb 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 183634e582..65f96ecacc 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 95c2c082f4..bc381fc542 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 a2920ce2ae..1e2f7b76d3 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 cf168d8daa..cb01219d8d 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 c8ea85d351..3211195487 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 26e33fc7b8..a149ceb29e 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 0000000000..bfd70093eb --- /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 0000000000..de0945cfe7 --- /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 5189017e39..20c3a90935 100644 --- a/src/frontend/yarn.lock +++ b/src/frontend/yarn.lock @@ -107,6 +107,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.0.tgz#55dad808d5bf3445a108eefc88ea3fdf034749a4" + integrity sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.0" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.27.3" + "@babel/helpers" "^7.27.6" + "@babel/parser" "^7.28.0" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.0" + "@babel/types" "^7.28.0" + 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.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.1.tgz#862d4fad858f7208edd487c28b58144036b76230" @@ -129,6 +150,17 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^3.0.2" +"@babel/generator@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.0.tgz#9cc2f7bd6eb054d77dc66c2664148a0c5118acd2" + integrity sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg== + dependencies: + "@babel/parser" "^7.28.0" + "@babel/types" "^7.28.0" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz#4345d81a9a46a6486e24d069469f13e60445c05d" @@ -180,6 +212,11 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + "@babel/helper-member-expression-to-functions@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz#ea1211276be93e798ce19037da6f06fbb994fa44" @@ -292,6 +329,14 @@ "@babel/template" "^7.27.2" "@babel/types" "^7.27.6" +"@babel/helpers@^7.27.6": + version "7.28.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.2.tgz#80f0918fecbfebea9af856c419763230040ee850" + integrity sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw== + 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.1", "@babel/parser@^7.27.2": version "7.27.2" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.2.tgz#577518bedb17a2ce4212afd052e01f7df0941127" @@ -306,6 +351,13 @@ dependencies: "@babel/types" "^7.27.3" +"@babel/parser@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.0.tgz#979829fbab51a29e13901e5a80713dbcb840825e" + integrity sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g== + dependencies: + "@babel/types" "^7.28.0" + "@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" @@ -803,6 +855,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" @@ -1074,6 +1140,19 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.0.tgz#518aa113359b062042379e333db18380b537e34b" + integrity sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.0" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.0" + 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.4.4": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" @@ -1090,6 +1169,14 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1" +"@babel/types@^7.28.0", "@babel/types@^7.28.2": + version "7.28.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" + integrity sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -2075,19 +2162,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" @@ -2284,6 +2358,14 @@ "@types/yargs" "^17.0.33" chalk "^4.1.2" +"@jridgewell/gen-mapping@^0.3.12": + version "0.3.12" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz#2234ce26c62889f03db3d7fea43c1932ab3e927b" + integrity sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/gen-mapping@^0.3.5": version "0.3.8" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" @@ -2332,6 +2414,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jridgewell/trace-mapping@^0.3.28": + version "0.3.29" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz#a58d31eaadaf92c6695680b2e1d464a9b8fbf7fc" + integrity sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@keyv/serialize@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@keyv/serialize/-/serialize-1.0.3.tgz#e0fe3710e2a379cb0490cd41e5a5ffa2bab58bf6" @@ -5540,6 +5630,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" @@ -6696,15 +6791,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" @@ -6927,11 +7013,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" @@ -7359,6 +7440,18 @@ resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.2.tgz#f755c5229f1401bbff7307d037c6e38fa169ad1d" integrity sha512-ryoo+EB19lMxAd80ln9BVf8pdOAxLb97amrQ3SFN9OCRn/5M5wvwDgAe4i8ZjhpbiHoDeP8yavcTEnpKBo7lZg== +"@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" @@ -10421,6 +10514,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" @@ -11489,17 +11587,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" @@ -11827,56 +11914,56 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsdom@^25.0.1: - version "25.0.1" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-25.0.1.tgz#536ec685c288fc8a5773a65f82d8b44badcc73ef" - integrity sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw== +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.1.0" + cssstyle "^4.2.1" data-urls "^5.0.0" - decimal.js "^10.4.3" - form-data "^4.0.0" + decimal.js "^10.5.0" html-encoding-sniffer "^4.0.0" http-proxy-agent "^7.0.2" - https-proxy-agent "^7.0.5" + https-proxy-agent "^7.0.6" is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.12" - parse5 "^7.1.2" - rrweb-cssom "^0.7.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.0.0" + 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.0.0" + whatwg-url "^14.1.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== +jsdom@^25.0.1: + version "25.0.1" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-25.0.1.tgz#536ec685c288fc8a5773a65f82d8b44badcc73ef" + integrity sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw== dependencies: - cssstyle "^4.2.1" + cssstyle "^4.1.0" data-urls "^5.0.0" - decimal.js "^10.5.0" + decimal.js "^10.4.3" + form-data "^4.0.0" html-encoding-sniffer "^4.0.0" http-proxy-agent "^7.0.2" - https-proxy-agent "^7.0.6" + https-proxy-agent "^7.0.5" is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.16" - parse5 "^7.2.1" - rrweb-cssom "^0.8.0" + nwsapi "^2.2.12" + parse5 "^7.1.2" + rrweb-cssom "^0.7.1" saxes "^6.0.0" symbol-tree "^3.2.4" - tough-cookie "^5.1.1" + tough-cookie "^5.0.0" 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" + whatwg-url "^14.0.0" ws "^8.18.0" xml-name-validator "^5.0.0" @@ -14090,6 +14177,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" @@ -15124,16 +15216,7 @@ string-length@^4.0.2: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -15259,14 +15342,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -15806,6 +15882,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" @@ -16343,6 +16424,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.0.0" resolved "https://registry.yarnpkg.com/vite/-/vite-7.0.0.tgz#5675bb4c956dd9da932583628e7758ab09fe761f" @@ -16816,16 +16906,7 @@ workbox-window@7.1.0: "@types/trusted-types" "^2.0.2" workbox-core "7.1.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==