♻️(docs-app) Switch from Jest tests to Vitest

We have migrated the testing framework from Jest
to Vitest for the Docs application.
This change includes updates to test files,
configuration files, and the addition of new setup
files for Vitest.
This commit is contained in:
Anthony LC
2025-08-06 14:35:43 +02:00
parent ff9e13ca03
commit 1eee24dc19
29 changed files with 325 additions and 277 deletions

View File

@@ -14,6 +14,7 @@ and this project adheres to
### Changed ### Changed
- ♻️(docs-app) Switch from Jest tests to Vitest #1269
- ⚡️(frontend) improve accessibility: - ⚡️(frontend) improve accessibility:
- #1248 - #1248
- #1235 - #1235

View File

@@ -1 +1,2 @@
NEXT_PUBLIC_API_ORIGIN=http://test.jest NEXT_PUBLIC_API_ORIGIN=http://test.jest
NEXT_PUBLIC_PUBLISH_AS_MIT=false

View File

@@ -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/(.*)$': '<rootDir>/src/features/docs/$1',
'^@/(.*)$': '<rootDir>/src/$1',
},
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
testEnvironment: 'jsdom',
};
const jestConfig = async () => {
const nextJestConfig = await createJestConfig(config)();
return {
...nextJestConfig,
moduleNameMapper: {
'\\.svg$': '<rootDir>/jest/mocks/svg.js',
'^.+\\.svg\\?url$': `<rootDir>/jest/mocks/fileMock.js`,
BlockNoteEditor: `<rootDir>/jest/mocks/ComponentMock.js`,
'custom-blocks': `<rootDir>/jest/mocks/ComponentMock.js`,
...nextJestConfig.moduleNameMapper,
},
};
};
export default jestConfig;

View File

@@ -1,4 +0,0 @@
import '@testing-library/jest-dom';
import * as dotenv from 'dotenv';
dotenv.config({ path: './.env.test' });

View File

@@ -1,5 +0,0 @@
import React from 'react';
export const ComponentMock = () => {
return <div>My component mocked</div>;
};

View File

@@ -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;
}

View File

@@ -1,3 +0,0 @@
const nameMock = 'svg';
export default nameMock;
export const ReactComponent = 'svg';

View File

@@ -11,8 +11,8 @@
"lint": "tsc --noEmit && next lint", "lint": "tsc --noEmit && next lint",
"prettier": "prettier --write .", "prettier": "prettier --write .",
"stylelint": "stylelint \"**/*.css\"", "stylelint": "stylelint \"**/*.css\"",
"test": "jest", "test": "vitest",
"test:watch": "jest --watch" "test:watch": "vitest --watch"
}, },
"dependencies": { "dependencies": {
"@ag-media/react-pdf-table": "2.0.3", "@ag-media/react-pdf-table": "2.0.3",
@@ -67,24 +67,25 @@
"@testing-library/jest-dom": "6.6.4", "@testing-library/jest-dom": "6.6.4",
"@testing-library/react": "16.3.0", "@testing-library/react": "16.3.0",
"@testing-library/user-event": "14.6.1", "@testing-library/user-event": "14.6.1",
"@types/jest": "30.0.0",
"@types/lodash": "4.17.20", "@types/lodash": "4.17.20",
"@types/luxon": "3.7.1", "@types/luxon": "3.7.1",
"@types/node": "*", "@types/node": "*",
"@types/react": "*", "@types/react": "*",
"@types/react-dom": "*", "@types/react-dom": "*",
"@vitejs/plugin-react": "4.7.0",
"cross-env": "10.0.0", "cross-env": "10.0.0",
"dotenv": "17.2.1", "dotenv": "17.2.1",
"eslint-config-impress": "*", "eslint-config-impress": "*",
"fetch-mock": "9.11.0", "fetch-mock": "9.11.0",
"jest": "30.0.5", "jsdom": "26.1.0",
"jest-environment-jsdom": "30.0.5",
"node-fetch": "2.7.0", "node-fetch": "2.7.0",
"prettier": "3.6.2", "prettier": "3.6.2",
"stylelint": "16.23.0", "stylelint": "16.23.0",
"stylelint-config-standard": "39.0.0", "stylelint-config-standard": "39.0.0",
"stylelint-prettier": "5.0.3", "stylelint-prettier": "5.0.3",
"typescript": "*", "typescript": "*",
"vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.4",
"webpack": "5.101.0", "webpack": "5.101.0",
"workbox-webpack-plugin": "7.1.0" "workbox-webpack-plugin": "7.1.0"
} }

View File

@@ -1,3 +1,5 @@
import { describe, expect, it } from 'vitest';
import { APIError, isAPIError } from '@/api'; import { APIError, isAPIError } from '@/api';
describe('APIError', () => { describe('APIError', () => {

View File

@@ -1,3 +1,5 @@
import { describe, expect, it } from 'vitest';
import { baseApiUrl } from '@/api'; import { baseApiUrl } from '@/api';
describe('config', () => { describe('config', () => {

View File

@@ -1,4 +1,5 @@
import fetchMock from 'fetch-mock'; import fetchMock from 'fetch-mock';
import { beforeEach, describe, expect, it } from 'vitest';
import { fetchAPI } from '@/api'; import { fetchAPI } from '@/api';

View File

@@ -1,5 +1,6 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { renderHook, waitFor } from '@testing-library/react'; import { renderHook, waitFor } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { useAPIInfiniteQuery } from '@/api'; import { useAPIInfiniteQuery } from '@/api';
@@ -21,8 +22,8 @@ const createWrapper = () => {
describe('helpers', () => { describe('helpers', () => {
it('fetches and paginates correctly', async () => { it('fetches and paginates correctly', async () => {
const mockAPI = jest const mockAPI = vi
.fn<Promise<DummyResponse>, [{ page: number; query: string }]>() .fn<(params: { page: number; query: string }) => Promise<DummyResponse>>()
.mockResolvedValueOnce({ .mockResolvedValueOnce({
results: [{ id: 1 }], results: [{ id: 1 }],
next: 'url?page=2', next: 'url?page=2',

View File

@@ -1,3 +1,5 @@
import { describe, expect, it } from 'vitest';
import { errorCauses, getCSRFToken } from '@/api'; import { errorCauses, getCSRFToken } from '@/api';
describe('utils', () => { describe('utils', () => {

View File

@@ -1,3 +1,4 @@
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { Box } from '../Box'; import { Box } from '../Box';

View File

@@ -1,42 +1,28 @@
import { Crisp } from 'crisp-sdk-web';
import fetchMock from 'fetch-mock'; import fetchMock from 'fetch-mock';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { gotoLogout } from '../utils'; import { gotoLogout } from '../utils';
jest.mock('crisp-sdk-web', () => ({ // Mock the Crisp service
...jest.requireActual('crisp-sdk-web'), vi.mock('@/services/Crisp', () => ({
Crisp: { terminateCrispSession: vi.fn(),
isCrispInjected: jest.fn().mockReturnValue(true),
setTokenId: jest.fn(),
user: {
setEmail: jest.fn(),
},
session: {
reset: jest.fn(),
},
},
})); }));
describe('utils', () => { describe('utils', () => {
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); vi.clearAllMocks();
fetchMock.restore(); fetchMock.restore();
}); });
it('checks support session is terminated when logout', () => { it('checks support session is terminated when logout', async () => {
jest.spyOn(console, 'error').mockImplementation(() => {}); const { terminateCrispSession } = await import('@/services/Crisp');
window.$crisp = true; // Mock window.location.replace
const propertyDescriptors = Object.getOwnPropertyDescriptors(window); const mockReplace = vi.fn();
for (const key in propertyDescriptors) { Object.defineProperty(window, 'location', {
propertyDescriptors[key].configurable = true;
}
const clonedWindow = Object.defineProperties({}, propertyDescriptors);
Object.defineProperty(clonedWindow, 'location', {
value: { value: {
...window.location, ...window.location,
replace: jest.fn(), replace: mockReplace,
}, },
writable: true, writable: true,
configurable: true, configurable: true,
@@ -44,6 +30,9 @@ describe('utils', () => {
gotoLogout(); gotoLogout();
expect(Crisp.session.reset).toHaveBeenCalled(); expect(terminateCrispSession).toHaveBeenCalled();
expect(mockReplace).toHaveBeenCalledWith(
'http://test.jest/api/v1.0/logout/',
);
}); });
}); });

View File

@@ -1,13 +1,14 @@
import { renderHook, waitFor } from '@testing-library/react'; import { renderHook, waitFor } from '@testing-library/react';
import fetchMock from 'fetch-mock'; import fetchMock from 'fetch-mock';
import { Fragment } from 'react'; import { Fragment } from 'react';
import { beforeEach, describe, expect, vi } from 'vitest';
import { AbstractAnalytic } from '@/libs'; import { AbstractAnalytic } from '@/libs';
import { AppWrapper } from '@/tests/utils'; import { AppWrapper } from '@/tests/utils';
import { useAuth } from '../useAuth'; import { useAuth } from '../useAuth';
const trackEventMock = jest.fn(); const trackEventMock = vi.fn();
const flag = true; const flag = true;
class TestAnalytic extends AbstractAnalytic { class TestAnalytic extends AbstractAnalytic {
public constructor() { public constructor() {
@@ -31,11 +32,11 @@ class TestAnalytic extends AbstractAnalytic {
} }
} }
jest.mock('next/router', () => ({ vi.mock('next/router', async () => ({
...jest.requireActual('next/router'), ...(await vi.importActual('next/router')),
useRouter: () => ({ useRouter: () => ({
pathname: '/dashboard', 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', () => { describe('useAuth hook - trackEvent effect', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); vi.clearAllMocks();
fetchMock.restore(); fetchMock.restore();
}); });

View File

@@ -1,36 +1,38 @@
import { act, renderHook, waitFor } from '@testing-library/react'; import { act, renderHook, waitFor } from '@testing-library/react';
import fetchMock from 'fetch-mock'; import fetchMock from 'fetch-mock';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { Mock, beforeEach, describe, expect, it, vi } from 'vitest';
import * as Y from 'yjs'; import * as Y from 'yjs';
import { AppWrapper } from '@/tests/utils'; import { AppWrapper } from '@/tests/utils';
import { useSaveDoc } from '../useSaveDoc'; import { useSaveDoc } from '../useSaveDoc';
jest.mock('next/router', () => ({ vi.mock('next/router', () => ({
useRouter: jest.fn(), useRouter: vi.fn(),
})); }));
jest.mock('@/docs/doc-versioning', () => ({ vi.mock('@/docs/doc-versioning', () => ({
KEY_LIST_DOC_VERSIONS: 'test-key-list-doc-versions', KEY_LIST_DOC_VERSIONS: 'test-key-list-doc-versions',
})); }));
jest.mock('@/docs/doc-management', () => ({ vi.mock('@/docs/doc-management', async () => ({
useUpdateDoc: jest.requireActual('@/docs/doc-management/api/useUpdateDoc') useUpdateDoc: (
.useUpdateDoc, await vi.importActual('@/docs/doc-management/api/useUpdateDoc')
).useUpdateDoc,
})); }));
describe('useSaveDoc', () => { describe('useSaveDoc', () => {
const mockRouterEvents = { const mockRouterEvents = {
on: jest.fn(), on: vi.fn(),
off: jest.fn(), off: vi.fn(),
}; };
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); vi.clearAllMocks();
fetchMock.restore(); fetchMock.restore();
(useRouter as jest.Mock).mockReturnValue({ (useRouter as Mock).mockReturnValue({
events: mockRouterEvents, events: mockRouterEvents,
}); });
}); });
@@ -39,7 +41,7 @@ describe('useSaveDoc', () => {
const yDoc = new Y.Doc(); const yDoc = new Y.Doc();
const docId = 'test-doc-id'; const docId = 'test-doc-id';
const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); const addEventListenerSpy = vi.spyOn(window, 'addEventListener');
renderHook(() => useSaveDoc(docId, yDoc, true, true), { renderHook(() => useSaveDoc(docId, yDoc, true, true), {
wrapper: AppWrapper, wrapper: AppWrapper,
@@ -60,8 +62,8 @@ describe('useSaveDoc', () => {
addEventListenerSpy.mockRestore(); addEventListenerSpy.mockRestore();
}); });
it('should not save when canSave is false', async () => { it('should not save when canSave is false', () => {
jest.useFakeTimers(); vi.useFakeTimers();
const yDoc = new Y.Doc(); const yDoc = new Y.Doc();
const docId = 'test-doc-id'; const docId = 'test-doc-id';
@@ -80,22 +82,19 @@ describe('useSaveDoc', () => {
act(() => { act(() => {
// Trigger a local update // Trigger a local update
yDoc.getMap('test').set('key', 'value'); yDoc.getMap('test').set('key', 'value');
// Advance timers to trigger the save interval
vi.advanceTimersByTime(61000);
}); });
act(() => { // Since canSave is false, no API call should be made
// Now advance timers after state has updated expect(fetchMock.calls().length).toBe(0);
jest.advanceTimersByTime(61000);
});
await waitFor(() => { vi.useRealTimers();
expect(fetchMock.calls().length).toBe(0);
});
jest.useRealTimers();
}); });
it('should save when there are local changes', async () => { it('should save when there are local changes', async () => {
jest.useFakeTimers(); vi.useFakeTimers();
const yDoc = new Y.Doc(); const yDoc = new Y.Doc();
const docId = 'test-doc-id'; const docId = 'test-doc-id';
@@ -117,21 +116,22 @@ describe('useSaveDoc', () => {
}); });
act(() => { act(() => {
// Now advance timers after state has updated // Advance timers to trigger the save interval
jest.advanceTimersByTime(61000); vi.advanceTimersByTime(61000);
}); });
// Switch to real timers to allow the mutation promise to resolve
vi.useRealTimers();
await waitFor(() => { await waitFor(() => {
expect(fetchMock.lastCall()?.[0]).toBe( expect(fetchMock.lastCall()?.[0]).toBe(
'http://test.jest/api/v1.0/documents/test-doc-id/', 'http://test.jest/api/v1.0/documents/test-doc-id/',
); );
}); });
jest.useRealTimers();
}); });
it('should not save when there are no local changes', async () => { it('should not save when there are no local changes', () => {
jest.useFakeTimers(); vi.useFakeTimers();
const yDoc = new Y.Doc(); const yDoc = new Y.Doc();
const docId = 'test-doc-id'; const docId = 'test-doc-id';
@@ -148,21 +148,20 @@ describe('useSaveDoc', () => {
}); });
act(() => { act(() => {
// Now advance timers after state has updated // Advance timers without triggering any local updates
jest.advanceTimersByTime(61000); vi.advanceTimersByTime(61000);
}); });
await waitFor(() => { // Since there are no local changes, no API call should be made
expect(fetchMock.calls().length).toBe(0); expect(fetchMock.calls().length).toBe(0);
});
jest.useRealTimers(); vi.useRealTimers();
}); });
it('should cleanup event listeners on unmount', () => { it('should cleanup event listeners on unmount', () => {
const yDoc = new Y.Doc(); const yDoc = new Y.Doc();
const docId = 'test-doc-id'; 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), { const { unmount } = renderHook(() => useSaveDoc(docId, yDoc, true, true), {
wrapper: AppWrapper, wrapper: AppWrapper,

View File

@@ -1,9 +1,10 @@
import { afterAll, afterEach, describe, expect, it, vi } from 'vitest';
const originalEnv = process.env.NEXT_PUBLIC_PUBLISH_AS_MIT; 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, anything: true,
})); }));
jest.mock('@/features/docs/doc-export/components/ModalExport', () => ({ vi.mock('@/features/docs/doc-export/components/ModalExport', () => ({
ModalExport: () => <span>ModalExport</span>, ModalExport: () => <span>ModalExport</span>,
})); }));
@@ -13,8 +14,8 @@ describe('useModuleExport', () => {
}); });
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); vi.clearAllMocks();
jest.resetModules(); vi.resetModules();
}); });
it('should return undefined when NEXT_PUBLIC_PUBLISH_AS_MIT is true', async () => { it('should return undefined when NEXT_PUBLIC_PUBLISH_AS_MIT is true', async () => {

View File

@@ -1,6 +1,7 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { beforeEach, describe, expect, vi } from 'vitest';
import { AbstractAnalytic, Analytics } from '@/libs'; import { AbstractAnalytic, Analytics } from '@/libs';
import { AppWrapper } from '@/tests/utils'; import { AppWrapper } from '@/tests/utils';
@@ -28,14 +29,10 @@ class TestAnalytic extends AbstractAnalytic {
} }
} }
jest.mock('@/features/docs/doc-export/', () => ({ vi.mock('next/router', async () => ({
ModalExport: () => <span>ModalExport</span>, ...(await vi.importActual('next/router')),
}));
jest.mock('next/router', () => ({
...jest.requireActual('next/router'),
useRouter: () => ({ useRouter: () => ({
push: jest.fn(), push: vi.fn(),
}), }),
})); }));

View File

@@ -1,34 +1,30 @@
/** import { afterEach, describe, expect, it, vi } from 'vitest';
* @jest-environment node
*/
import '@testing-library/jest-dom';
import { RequestSerializer } from '../RequestSerializer'; import { RequestSerializer } from '../RequestSerializer';
import { ApiPlugin } from '../plugins/ApiPlugin'; import { ApiPlugin } from '../plugins/ApiPlugin';
const mockedGet = jest.fn().mockResolvedValue({}); const mockedGet = vi.fn().mockResolvedValue({});
const mockedGetAllKeys = jest.fn().mockResolvedValue([]); const mockedGetAllKeys = vi.fn().mockResolvedValue([]);
const mockedPut = jest.fn().mockResolvedValue({}); const mockedPut = vi.fn().mockResolvedValue({});
const mockedDelete = jest.fn().mockResolvedValue({}); const mockedDelete = vi.fn().mockResolvedValue({});
const mockedClose = jest.fn().mockResolvedValue({}); const mockedClose = vi.fn().mockResolvedValue({});
const mockedOpendDB = jest.fn().mockResolvedValue({ const mockedOpendDB = vi.fn().mockResolvedValue({
get: mockedGet, get: mockedGet,
getAllKeys: mockedGetAllKeys, getAllKeys: mockedGetAllKeys,
getAll: jest.fn().mockResolvedValue([]), getAll: vi.fn().mockResolvedValue([]),
put: mockedPut, put: mockedPut,
delete: mockedDelete, delete: mockedDelete,
clear: jest.fn().mockResolvedValue({}), clear: vi.fn().mockResolvedValue({}),
close: mockedClose, close: mockedClose,
}); });
jest.mock('idb', () => ({ vi.mock('idb', async () => ({
...jest.requireActual('idb'), ...(await vi.importActual('idb')),
openDB: () => mockedOpendDB(), openDB: () => mockedOpendDB(),
})); }));
describe('ApiPlugin', () => { describe('ApiPlugin', () => {
afterEach(() => jest.clearAllMocks()); afterEach(() => vi.clearAllMocks());
[ [
{ type: 'item', table: 'doc-item' }, { type: 'item', table: 'doc-item' },
@@ -36,7 +32,7 @@ describe('ApiPlugin', () => {
{ type: 'update', table: 'doc-item' }, { type: 'update', table: 'doc-item' },
].forEach(({ type, table }) => { ].forEach(({ type, table }) => {
it(`calls fetchDidSucceed with type ${type} and status 200`, async () => { it(`calls fetchDidSucceed with type ${type} and status 200`, async () => {
const mockedSync = jest.fn().mockResolvedValue({}); const mockedSync = vi.fn().mockResolvedValue({});
const apiPlugin = new ApiPlugin({ const apiPlugin = new ApiPlugin({
tableName: table as any, tableName: table as any,
type: type as any, type: type as any,
@@ -55,7 +51,7 @@ describe('ApiPlugin', () => {
json: () => body, json: () => body,
} as unknown as Request, } as unknown as Request,
} as any; } as any;
const mockedClone = jest.fn().mockReturnValue(requestInit.request); const mockedClone = vi.fn().mockReturnValue(requestInit.request);
await apiPlugin.requestWillFetch?.(requestInit); await apiPlugin.requestWillFetch?.(requestInit);
const response = await apiPlugin.fetchDidSucceed?.({ const response = await apiPlugin.fetchDidSucceed?.({
@@ -81,7 +77,7 @@ describe('ApiPlugin', () => {
const apiPlugin = new ApiPlugin({ const apiPlugin = new ApiPlugin({
tableName: table as any, tableName: table as any,
type: type as any, type: type as any,
syncManager: jest.fn() as any, syncManager: vi.fn() as any,
}); });
const body = { lastName: 'Doe' }; const body = { lastName: 'Doe' };
@@ -114,7 +110,7 @@ describe('ApiPlugin', () => {
{ type: 'item', withClone: false }, { type: 'item', withClone: false },
].forEach(({ type, withClone }) => { ].forEach(({ type, withClone }) => {
it(`calls requestWillFetch with type ${type}`, async () => { it(`calls requestWillFetch with type ${type}`, async () => {
const mockedSync = jest.fn().mockResolvedValue({}); const mockedSync = vi.fn().mockResolvedValue({});
const apiPlugin = new ApiPlugin({ const apiPlugin = new ApiPlugin({
type: 'update', type: 'update',
@@ -123,7 +119,7 @@ describe('ApiPlugin', () => {
} as any, } as any,
}); });
const mockedClone = jest.fn().mockResolvedValue({}); const mockedClone = vi.fn().mockResolvedValue({});
const requestInit = { const requestInit = {
request: { request: {
url: 'test-url', url: 'test-url',
@@ -189,9 +185,9 @@ describe('ApiPlugin', () => {
} as unknown as Request, } as unknown as Request,
} as any; } 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({ const apiPlugin = new ApiPlugin({
type: 'update', type: 'update',
syncManager: { syncManager: {
@@ -265,9 +261,9 @@ describe('ApiPlugin', () => {
} as unknown as Request, } as unknown as Request,
} as any; } 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({ const apiPlugin = new ApiPlugin({
type: 'delete', type: 'delete',
syncManager: { syncManager: {
@@ -334,7 +330,7 @@ describe('ApiPlugin', () => {
Object.defineProperty(global, 'self', { Object.defineProperty(global, 'self', {
value: { value: {
crypto: { crypto: {
randomUUID: jest.fn().mockReturnValue('444555'), randomUUID: vi.fn().mockReturnValue('444555'),
}, },
}, },
}); });
@@ -351,9 +347,9 @@ describe('ApiPlugin', () => {
} as unknown as Request, } as unknown as Request,
} as any; } 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({ const apiPlugin = new ApiPlugin({
type: 'create', type: 'create',
syncManager: { syncManager: {

View File

@@ -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 { MESSAGE_TYPE } from '../conf';
import { OfflinePlugin } from '../plugins/OfflinePlugin'; import { OfflinePlugin } from '../plugins/OfflinePlugin';
const mockServiceWorkerScope = { const mockServiceWorkerScope = {
clients: { clients: {
matchAll: jest.fn().mockResolvedValue([]), matchAll: vi.fn().mockResolvedValue([]),
}, },
} as unknown as ServiceWorkerGlobalScope; } as unknown as ServiceWorkerGlobalScope;
@@ -19,11 +18,11 @@ const mockServiceWorkerScope = {
} as unknown as ServiceWorkerGlobalScope; } as unknown as ServiceWorkerGlobalScope;
describe('OfflinePlugin', () => { describe('OfflinePlugin', () => {
afterEach(() => jest.clearAllMocks()); afterEach(() => vi.clearAllMocks());
it(`calls fetchDidSucceed`, async () => { it(`calls fetchDidSucceed`, async () => {
const apiPlugin = new OfflinePlugin(); const apiPlugin = new OfflinePlugin();
const postMessageSpy = jest.spyOn(apiPlugin, 'postMessage'); const postMessageSpy = vi.spyOn(apiPlugin, 'postMessage');
await apiPlugin.fetchDidSucceed?.({ await apiPlugin.fetchDidSucceed?.({
response: new Response(), response: new Response(),
@@ -34,7 +33,7 @@ describe('OfflinePlugin', () => {
it(`calls fetchDidFail`, async () => { it(`calls fetchDidFail`, async () => {
const apiPlugin = new OfflinePlugin(); const apiPlugin = new OfflinePlugin();
const postMessageSpy = jest.spyOn(apiPlugin, 'postMessage'); const postMessageSpy = vi.spyOn(apiPlugin, 'postMessage');
await apiPlugin.fetchDidFail?.({} as any); await apiPlugin.fetchDidFail?.({} as any);
@@ -43,12 +42,9 @@ describe('OfflinePlugin', () => {
it(`calls postMessage`, async () => { it(`calls postMessage`, async () => {
const apiPlugin = new OfflinePlugin(); const apiPlugin = new OfflinePlugin();
const mockClients = [ const mockClients = [{ postMessage: vi.fn() }, { postMessage: vi.fn() }];
{ postMessage: jest.fn() },
{ postMessage: jest.fn() },
];
mockServiceWorkerScope.clients.matchAll = jest mockServiceWorkerScope.clients.matchAll = vi
.fn() .fn()
.mockResolvedValue(mockClients); .mockResolvedValue(mockClients);

View File

@@ -1,24 +1,23 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
/** /**
* @jest-environment node * @jest-environment node
*/ */
import '@testing-library/jest-dom';
import { SyncManager } from '../SyncManager'; import { SyncManager } from '../SyncManager';
const mockedSleep = jest.fn(); const mockedSleep = vi.fn();
jest.mock('@/utils/system', () => ({ vi.mock('@/utils/system', () => ({
sleep: jest.fn().mockImplementation((ms) => mockedSleep(ms)), sleep: vi.fn().mockImplementation((ms) => mockedSleep(ms)),
})); }));
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
describe('SyncManager', () => { describe('SyncManager', () => {
afterEach(() => jest.clearAllMocks()); afterEach(() => vi.clearAllMocks());
it('checks SyncManager no sync to do', async () => { it('checks SyncManager no sync to do', async () => {
const toSync = jest.fn(); const toSync = vi.fn();
const hasSyncToDo = jest.fn().mockResolvedValue(false); const hasSyncToDo = vi.fn().mockResolvedValue(false);
new SyncManager(toSync, hasSyncToDo); new SyncManager(toSync, hasSyncToDo);
await delay(100); await delay(100);
@@ -28,8 +27,8 @@ describe('SyncManager', () => {
}); });
it('checks SyncManager sync to do', async () => { it('checks SyncManager sync to do', async () => {
const toSync = jest.fn(); const toSync = vi.fn();
const hasSyncToDo = jest.fn().mockResolvedValue(true); const hasSyncToDo = vi.fn().mockResolvedValue(true);
new SyncManager(toSync, hasSyncToDo); new SyncManager(toSync, hasSyncToDo);
await delay(100); await delay(100);
@@ -39,10 +38,10 @@ describe('SyncManager', () => {
}); });
it('checks SyncManager sync to do trigger error', async () => { 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 toSync = vi.fn().mockRejectedValue(new Error('error'));
const hasSyncToDo = jest.fn().mockResolvedValue(true); const hasSyncToDo = vi.fn().mockResolvedValue(true);
new SyncManager(toSync, hasSyncToDo); new SyncManager(toSync, hasSyncToDo);
await delay(100); await delay(100);
@@ -56,8 +55,8 @@ describe('SyncManager', () => {
}); });
it('checks SyncManager multiple sync to do', async () => { it('checks SyncManager multiple sync to do', async () => {
const toSync = jest.fn().mockReturnValue(delay(200)); const toSync = vi.fn().mockReturnValue(delay(200));
const hasSyncToDo = jest.fn().mockResolvedValue(true); const hasSyncToDo = vi.fn().mockResolvedValue(true);
const syncManager = new SyncManager(toSync, hasSyncToDo); const syncManager = new SyncManager(toSync, hasSyncToDo);
await syncManager.sync(); await syncManager.sync();

View File

@@ -1,11 +1,11 @@
import '@testing-library/jest-dom';
import { act, renderHook } from '@testing-library/react'; import { act, renderHook } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { MESSAGE_TYPE } from '../conf'; import { MESSAGE_TYPE } from '../conf';
import { useIsOffline, useOffline } from '../hooks/useOffline'; import { useIsOffline, useOffline } from '../hooks/useOffline';
const mockAddEventListener = jest.fn(); const mockAddEventListener = vi.fn();
const mockRemoveEventListener = jest.fn(); const mockRemoveEventListener = vi.fn();
Object.defineProperty(navigator, 'serviceWorker', { Object.defineProperty(navigator, 'serviceWorker', {
value: { value: {
addEventListener: mockAddEventListener, addEventListener: mockAddEventListener,
@@ -16,7 +16,7 @@ Object.defineProperty(navigator, 'serviceWorker', {
describe('useOffline', () => { describe('useOffline', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); vi.clearAllMocks();
}); });
it('should set isOffline to true when receiving an offline message', () => { it('should set isOffline to true when receiving an offline message', () => {

View File

@@ -1,5 +1,5 @@
import '@testing-library/jest-dom';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { useSWRegister } from '../hooks/useSWRegister'; import { useSWRegister } from '../hooks/useSWRegister';
@@ -12,9 +12,9 @@ const TestComponent = () => {
describe('useSWRegister', () => { describe('useSWRegister', () => {
it('checks service-worker is register', () => { it('checks service-worker is register', () => {
process.env.NEXT_PUBLIC_BUILD_ID = '123456'; 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( registerSpy.mockImplementation(
() => () =>
new Promise((reject) => { new Promise((reject) => {
@@ -22,11 +22,13 @@ describe('useSWRegister', () => {
}), }),
); );
const addEventListenerSpy = vi.fn();
Object.defineProperty(navigator, 'serviceWorker', { Object.defineProperty(navigator, 'serviceWorker', {
value: { value: {
register: registerSpy, register: registerSpy,
addEventListener: jest.fn(), addEventListener: addEventListenerSpy,
removeEventListener: jest.fn(), removeEventListener: vi.fn(),
}, },
writable: true, writable: true,
}); });
@@ -34,7 +36,7 @@ describe('useSWRegister', () => {
render(<TestComponent />); render(<TestComponent />);
expect(registerSpy).toHaveBeenCalledWith('/service-worker.js?v=123456'); expect(registerSpy).toHaveBeenCalledWith('/service-worker.js?v=123456');
expect(navigator.serviceWorker.addEventListener).toHaveBeenCalledWith( expect(addEventListenerSpy).toHaveBeenCalledWith(
'controllerchange', 'controllerchange',
expect.any(Function), expect.any(Function),
); );
@@ -44,7 +46,7 @@ describe('useSWRegister', () => {
process.env.NEXT_PUBLIC_SW_DEACTIVATED = 'true'; process.env.NEXT_PUBLIC_SW_DEACTIVATED = 'true';
process.env.NEXT_PUBLIC_BUILD_ID = '123456'; process.env.NEXT_PUBLIC_BUILD_ID = '123456';
const registerSpy = jest.fn(); const registerSpy = vi.fn();
registerSpy.mockImplementation( registerSpy.mockImplementation(
() => () =>
new Promise((reject) => { new Promise((reject) => {

View File

@@ -1,4 +1,4 @@
import '@testing-library/jest-dom'; import { describe, expect, it } from 'vitest';
import { isValidEmail } from '../string'; import { isValidEmail } from '../string';

View File

@@ -1,3 +1,5 @@
import { describe, expect, it } from 'vitest';
import { isSafeUrl } from '@/utils/url'; import { isSafeUrl } from '@/utils/url';
describe('isSafeUrl', () => { describe('isSafeUrl', () => {

View File

@@ -0,0 +1,25 @@
/// <reference types="vitest" />
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',
},
});

View File

@@ -0,0 +1,4 @@
import '@testing-library/jest-dom/vitest';
import * as dotenv from 'dotenv';
dotenv.config({ path: './.env.test', quiet: true });

View File

@@ -75,6 +75,27 @@
json5 "^2.2.3" json5 "^2.2.3"
semver "^6.3.1" 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": "@babel/generator@^7.27.5", "@babel/generator@^7.28.0":
version "7.28.0" version "7.28.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.0.tgz#9cc2f7bd6eb054d77dc66c2664148a0c5118acd2" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.0.tgz#9cc2f7bd6eb054d77dc66c2664148a0c5118acd2"
@@ -86,6 +107,17 @@
"@jridgewell/trace-mapping" "^0.3.28" "@jridgewell/trace-mapping" "^0.3.28"
jsesc "^3.0.2" 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": "@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3":
version "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" 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/helper-validator-identifier" "^7.27.1"
"@babel/traverse" "^7.27.3" "@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": "@babel/helper-optimise-call-expression@^7.27.1":
version "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" 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/template" "^7.27.2"
"@babel/types" "^7.28.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": "@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" version "7.28.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.0.tgz#979829fbab51a29e13901e5a80713dbcb840825e" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.0.tgz#979829fbab51a29e13901e5a80713dbcb840825e"
@@ -244,6 +293,13 @@
dependencies: dependencies:
"@babel/types" "^7.28.0" "@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": "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1":
version "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" 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: dependencies:
"@babel/plugin-transform-react-jsx" "^7.27.1" "@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": "@babel/plugin-transform-react-jsx@^7.27.1":
version "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" 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" "@babel/types" "^7.28.0"
debug "^4.3.1" 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": "@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" version "7.28.2"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" 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" resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be"
integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== 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": "@jest/environment@30.0.5":
version "30.0.5" version "30.0.5"
resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.0.5.tgz#eaaae0403c7d3f8414053c2224acc3011e1c3a1b" 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" resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f"
integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg== 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": "@rollup/plugin-babel@^5.2.0":
version "5.3.1" version "5.3.1"
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
@@ -5346,15 +5421,6 @@
expect "^30.0.0" expect "^30.0.0"
pretty-format "^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": "@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.9":
version "7.0.15" version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
@@ -5577,11 +5643,6 @@
dependencies: dependencies:
"@types/node" "*" "@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": "@types/trusted-types@^2.0.2":
version "2.0.7" version "2.0.7"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" 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" 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== 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": "@vitest/expect@3.2.4":
version "3.2.4" version "3.2.4"
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-3.2.4.tgz#8362124cd811a5ee11c5768207b9df53d34f2433" 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" resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43"
integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg== 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: gopd@^1.0.1, gopd@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" 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" jest-util "30.0.5"
pretty-format "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: jest-environment-node@30.0.5:
version "30.0.5" version "30.0.5"
resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-30.0.5.tgz#6a98dd80e0384ead67ed05643381395f6cda93c9" 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: dependencies:
argparse "^2.0.1" 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: jsdom@^25.0.1:
version "25.0.1" version "25.0.1"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-25.0.1.tgz#536ec685c288fc8a5773a65f82d8b44badcc73ef" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-25.0.1.tgz#536ec685c288fc8a5773a65f82d8b44badcc73ef"
@@ -10107,32 +10200,6 @@ jsdom@^25.0.1:
ws "^8.18.0" ws "^8.18.0"
xml-name-validator "^5.0.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: jsesc@^3.0.2:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" 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" resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-5.4.4.tgz#d31f0e260609431500c8d3f81bbd3ae1fb7cacad"
integrity sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA== 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: react-remove-scroll-bar@^2.3.7:
version "2.3.8" version "2.3.8"
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz#99c20f908ee467b385b68a3469b4a3e750012223" 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" normalize-path "^3.0.0"
plimit-lit "^1.2.6" 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: tsconfig-paths@^3.15.0:
version "3.15.0" version "3.15.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" 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" pathe "^2.0.3"
vite "^5.0.0 || ^6.0.0 || ^7.0.0-0" 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": "vite@^5.0.0 || ^6.0.0 || ^7.0.0-0":
version "7.1.0" version "7.1.0"
resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.0.tgz#6fb13c74c13cfdd0e200ee61d6ea6e8fafc2e8b5" resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.0.tgz#6fb13c74c13cfdd0e200ee61d6ea6e8fafc2e8b5"