(null)
- /* v8 ignore next -- defensive non-browser fallback; this client-only plugin runs where document exists (browser/jsdom). @preserve */
+ /* v8 ignore next -- defensive non-browser fallback; this client-only plugin runs where document exists (browser/test DOM runtime). @preserve */
const containerEl = useMemo(() => container ?? (typeof document !== 'undefined' ? document.body : null), [container])
const useContainer = !!containerEl && containerEl !== document.body
@@ -210,7 +210,7 @@ export default function ShortcutsPopupPlugin({
if (rect.width === 0 && rect.height === 0) {
const root = editor.getRootElement()
- /* v8 ignore next 10 -- zero-size rect recovery depends on browser layout/selection geometry; deterministic reproduction in jsdom is unreliable. @preserve */
+ /* v8 ignore next 10 -- zero-size rect recovery depends on browser layout/selection geometry; deterministic reproduction in the test DOM runtime is unreliable. @preserve */
if (root) {
const sc = range.startContainer
const node = sc.nodeType === Node.ELEMENT_NODE
diff --git a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/__tests__/index.spec.tsx b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/__tests__/index.spec.tsx
index 7f292c8ff9..0a3470420c 100644
--- a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/__tests__/index.spec.tsx
+++ b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/__tests__/index.spec.tsx
@@ -1612,9 +1612,7 @@ describe('Uploader', () => {
if (!dropArea)
return
- fireEvent.drop(dropArea, {
- dataTransfer: null,
- })
+ fireEvent.drop(dropArea)
expect(updateFile).not.toHaveBeenCalled()
})
diff --git a/web/app/components/datasets/create/website/watercrawl/__tests__/index.spec.tsx b/web/app/components/datasets/create/website/watercrawl/__tests__/index.spec.tsx
index 5ff2d8efb8..e9d933cc03 100644
--- a/web/app/components/datasets/create/website/watercrawl/__tests__/index.spec.tsx
+++ b/web/app/components/datasets/create/website/watercrawl/__tests__/index.spec.tsx
@@ -1,6 +1,3 @@
-/**
- * @vitest-environment jsdom
- */
import type { Mock } from 'vitest'
import type { CrawlOptions, CrawlResultItem } from '@/models/datasets'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
diff --git a/web/app/components/develop/__tests__/code.spec.tsx b/web/app/components/develop/__tests__/code.spec.tsx
index 452e6ea98f..e5eaebb600 100644
--- a/web/app/components/develop/__tests__/code.spec.tsx
+++ b/web/app/components/develop/__tests__/code.spec.tsx
@@ -11,7 +11,7 @@ describe('code.tsx components', () => {
vi.clearAllMocks()
vi.spyOn(console, 'error').mockImplementation(() => {})
vi.useFakeTimers({ shouldAdvanceTime: true })
- // jsdom does not implement scrollBy; mock it to prevent stderr noise
+ // The test DOM runtime does not implement scrollBy; mock it to prevent stderr noise
window.scrollBy = vi.fn()
})
diff --git a/web/app/components/develop/__tests__/use-doc-toc.spec.ts b/web/app/components/develop/__tests__/use-doc-toc.spec.ts
index e437e13065..b20c2c8ecf 100644
--- a/web/app/components/develop/__tests__/use-doc-toc.spec.ts
+++ b/web/app/components/develop/__tests__/use-doc-toc.spec.ts
@@ -307,7 +307,7 @@ describe('useDocToc', () => {
it('should update activeSection when scrolling past a section', async () => {
vi.useFakeTimers()
- // innerHeight/2 = 384 in jsdom (default 768), so top <= 384 means "scrolled past"
+ // innerHeight/2 = 384 with the default test viewport height (768), so top <= 384 means "scrolled past"
const { scrollContainer, cleanup } = setupScrollDOM([
{ id: 'intro', text: 'Intro', top: 100 },
{ id: 'details', text: 'Details', top: 600 },
diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-custom-model.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-custom-model.spec.tsx
index 6117420afa..43a27dac9b 100644
--- a/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-custom-model.spec.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-custom-model.spec.tsx
@@ -43,7 +43,7 @@ vi.mock('@/app/components/base/tooltip', () => ({
),
}))
-// Mock portal components to avoid async/jsdom issues (consistent with sibling tests)
+// Mock portal components to avoid async test DOM issues (consistent with sibling tests)
vi.mock('@/app/components/base/portal-to-follow-elem', () => ({
PortalToFollowElem: ({ children, open }: { children: React.ReactNode, open: boolean, onOpenChange: (open: boolean) => void }) => (
diff --git a/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-card.spec.tsx b/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-card.spec.tsx
index 237c72adf0..2af14c5864 100644
--- a/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-card.spec.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-card.spec.tsx
@@ -142,7 +142,7 @@ describe('EndpointCard', () => {
failureFlags.disable = false
failureFlags.delete = false
failureFlags.update = false
- // Polyfill document.execCommand for copy-to-clipboard in jsdom
+ // Polyfill document.execCommand for copy-to-clipboard in the test DOM runtime
if (typeof document.execCommand !== 'function') {
document.execCommand = vi.fn().mockReturnValue(true)
}
diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/oauth-client.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/oauth-client.spec.tsx
index ce53bf5b9a..5c4407b3c5 100644
--- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/oauth-client.spec.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/oauth-client.spec.tsx
@@ -102,10 +102,12 @@ vi.mock('@/app/components/base/ui/toast', () => ({
}))
const mockClipboardWriteText = vi.fn()
-Object.assign(navigator, {
- clipboard: {
+Object.defineProperty(navigator, 'clipboard', {
+ value: {
writeText: mockClipboardWriteText,
},
+ configurable: true,
+ writable: true,
})
vi.mock('@/app/components/base/modal/modal', () => ({
@@ -192,6 +194,13 @@ describe('OAuthClientSettingsModal', () => {
vi.clearAllMocks()
mockUsePluginStore.mockReturnValue(mockPluginDetail)
mockClipboardWriteText.mockResolvedValue(undefined)
+ Object.defineProperty(navigator, 'clipboard', {
+ value: {
+ writeText: mockClipboardWriteText,
+ },
+ configurable: true,
+ writable: true,
+ })
setMockFormValues({
values: { client_id: 'test-client-id', client_secret: 'test-client-secret' },
isCheckValidated: true,
diff --git a/web/app/components/workflow/__tests__/custom-edge-linear-gradient-render.spec.tsx b/web/app/components/workflow/__tests__/custom-edge-linear-gradient-render.spec.tsx
index e962923158..973dfacbc8 100644
--- a/web/app/components/workflow/__tests__/custom-edge-linear-gradient-render.spec.tsx
+++ b/web/app/components/workflow/__tests__/custom-edge-linear-gradient-render.spec.tsx
@@ -48,10 +48,10 @@ describe('CustomEdgeLinearGradientRender', () => {
const stops = container.querySelectorAll('stop')
expect(stops).toHaveLength(2)
expect(stops[0]).toHaveAttribute('offset', '0%')
- expect(stops[0].getAttribute('style')).toContain('stop-color: rgb(17, 17, 17)')
+ expect(stops[0].getAttribute('style')).toContain('stop-color: #111111')
expect(stops[0].getAttribute('style')).toContain('stop-opacity: 1')
expect(stops[1]).toHaveAttribute('offset', '100%')
- expect(stops[1].getAttribute('style')).toContain('stop-color: rgb(34, 34, 34)')
+ expect(stops[1].getAttribute('style')).toContain('stop-color: #222222')
expect(stops[1].getAttribute('style')).toContain('stop-opacity: 1')
})
})
diff --git a/web/app/components/workflow/__tests__/update-dsl-modal.spec.tsx b/web/app/components/workflow/__tests__/update-dsl-modal.spec.tsx
index 82645f2028..961ab6ddb4 100644
--- a/web/app/components/workflow/__tests__/update-dsl-modal.spec.tsx
+++ b/web/app/components/workflow/__tests__/update-dsl-modal.spec.tsx
@@ -209,7 +209,7 @@ describe('UpdateDSLModal', () => {
})
await waitFor(() => {
- expect(screen.getByRole('button', { name: 'app.newApp.Cancel' })).toBeInTheDocument()
+ expect(screen.getByRole('button', { name: 'app.newApp.Confirm' })).toBeInTheDocument()
}, { timeout: 1000 })
fireEvent.click(screen.getByRole('button', { name: 'app.newApp.Cancel' }))
diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/__tests__/integration.spec.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/__tests__/integration.spec.tsx
index 00a6cbbe29..f2441e78d0 100644
--- a/web/app/components/workflow/nodes/trigger-schedule/components/__tests__/integration.spec.tsx
+++ b/web/app/components/workflow/nodes/trigger-schedule/components/__tests__/integration.spec.tsx
@@ -1,6 +1,6 @@
/* eslint-disable ts/no-explicit-any */
import type { ScheduleTriggerNodeType } from '../../types'
-import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
+import { render, screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import FrequencySelector from '../frequency-selector'
import ModeSwitcher from '../mode-switcher'
@@ -44,14 +44,14 @@ describe('trigger-schedule components', () => {
)
const trigger = screen.getByRole('button', { name: 'workflow.nodes.triggerSchedule.frequency.daily' })
- fireEvent.click(trigger)
+ await user.click(trigger)
await waitFor(() => {
expect(trigger).toHaveAttribute('aria-expanded', 'true')
})
const listbox = await screen.findByRole('listbox')
- await user.click(within(listbox).getByText('workflow.nodes.triggerSchedule.frequency.weekly'))
+ await user.click(within(listbox).getByRole('option', { name: 'workflow.nodes.triggerSchedule.frequency.weekly' }))
await waitFor(() => {
expect(onChange).toHaveBeenCalledWith('weekly')
diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/variable-modal.spec.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/variable-modal.spec.tsx
index 319e3803f4..297b534a6a 100644
--- a/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/variable-modal.spec.tsx
+++ b/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/variable-modal.spec.tsx
@@ -150,7 +150,7 @@ describe('variable-modal', () => {
await user.click(screen.getByText('workflow.chatVariable.modal.editInJSON'))
await waitFor(() => {
- expect(screen.getByText('Loading...')).toBeInTheDocument()
+ expect(screen.getByTestId('monaco-editor')).toBeInTheDocument()
})
await user.click(screen.getByText('workflow.chatVariable.modal.editInForm'))
expect(screen.getByDisplayValue('enabled')).toBeInTheDocument()
diff --git a/web/docs/test.md b/web/docs/test.md
index cb22b73b15..bc1546a991 100644
--- a/web/docs/test.md
+++ b/web/docs/test.md
@@ -8,7 +8,7 @@ When I ask you to write/refactor/fix tests, follow these rules by default.
- **Framework**: Next.js 15 + React 19 + TypeScript
- **Testing Tools**: Vitest 4.0.16 + React Testing Library 16.0
-- **Test Environment**: jsdom
+- **Test Environment**: happy-dom
- **File Naming**: `ComponentName.spec.tsx` inside a same-level `__tests__/` directory
- **Placement Rule**: Component, hook, and utility tests must live in a sibling `__tests__/` folder at the same level as the source under test. For example, `foo/index.tsx` maps to `foo/__tests__/index.spec.tsx`, and `foo/bar.ts` maps to `foo/__tests__/bar.spec.ts`.
@@ -30,7 +30,7 @@ pnpm test path/to/file.spec.tsx
## Project Test Setup
-- **Configuration**: `vitest.config.ts` sets the `jsdom` environment, loads the Testing Library presets, and respects our path aliases (`@/...`). Check this file before adding new transformers or module name mappers.
+- **Configuration**: `vite.config.ts` sets the `happy-dom` environment, loads the Testing Library presets, and respects our path aliases (`@/...`). Check this file before adding new transformers or module name mappers.
- **Global setup**: `vitest.setup.ts` already imports `@testing-library/jest-dom`, runs `cleanup()` after every test, and defines shared mocks (for example `react-i18next`). Add any environment-level mocks (for example `ResizeObserver`, `matchMedia`, `IntersectionObserver`, `TextEncoder`, `crypto`) here so they are shared consistently.
- **Reusable mocks**: Place shared mock factories inside `web/__mocks__/` and use `vi.mock('module-name')` to point to them rather than redefining mocks in every spec.
- **Mocking behavior**: Modules are not mocked automatically. Use `vi.mock(...)` in tests, or place global mocks in `vitest.setup.ts`.
diff --git a/web/package.json b/web/package.json
index 361f8e2e0e..76cfac4eba 100644
--- a/web/package.json
+++ b/web/package.json
@@ -220,11 +220,10 @@
"eslint-plugin-react-refresh": "0.5.2",
"eslint-plugin-sonarjs": "4.0.2",
"eslint-plugin-storybook": "10.3.1",
+ "happy-dom": "20.8.8",
"hono": "4.12.8",
"husky": "9.1.7",
"iconify-import-svg": "0.1.2",
- "jsdom": "29.0.1",
- "jsdom-testing-mocks": "1.16.0",
"knip": "6.0.2",
"lint-staged": "16.4.0",
"postcss": "8.5.8",
diff --git a/web/plugins/dev-proxy/server.spec.ts b/web/plugins/dev-proxy/server.spec.ts
index 9c950abae0..c57ec8b4fe 100644
--- a/web/plugins/dev-proxy/server.spec.ts
+++ b/web/plugins/dev-proxy/server.spec.ts
@@ -1,3 +1,6 @@
+/**
+ * @vitest-environment node
+ */
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { buildUpstreamUrl, createDevProxyApp, isAllowedDevOrigin, resolveDevProxyTargets } from './server'
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index 191826d80d..8fd930e2b8 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -371,7 +371,7 @@ importers:
devDependencies:
'@antfu/eslint-config':
specifier: 7.7.3
- version: 7.7.3(@eslint-react/eslint-plugin@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.2.1)(@typescript-eslint/rule-tester@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.1.0(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.1.0(jiti@1.21.7)))(eslint@10.1.0(jiti@1.21.7))(oxlint@1.56.0(oxlint-tsgolint@0.17.1))(typescript@5.9.3)
+ version: 7.7.3(@eslint-react/eslint-plugin@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.2.1)(@typescript-eslint/rule-tester@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.1.0(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.1.0(jiti@1.21.7)))(eslint@10.1.0(jiti@1.21.7))(oxlint@1.56.0(oxlint-tsgolint@0.17.1))(typescript@5.9.3)
'@chromatic-com/storybook':
specifier: 5.0.2
version: 5.0.2(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))
@@ -506,7 +506,7 @@ importers:
version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)
'@vitest/coverage-v8':
specifier: 4.1.0
- version: 4.1.0(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))
+ version: 4.1.0(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))
agentation:
specifier: 2.3.3
version: 2.3.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -546,6 +546,9 @@ importers:
eslint-plugin-storybook:
specifier: 10.3.1
version: 10.3.1(eslint@10.1.0(jiti@1.21.7))(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)
+ happy-dom:
+ specifier: 20.8.8
+ version: 20.8.8
hono:
specifier: 4.12.8
version: 4.12.8
@@ -555,12 +558,6 @@ importers:
iconify-import-svg:
specifier: 0.1.2
version: 0.1.2
- jsdom:
- specifier: 29.0.1
- version: 29.0.1(canvas@3.2.2)
- jsdom-testing-mocks:
- specifier: 1.16.0
- version: 1.16.0
knip:
specifier: 6.0.2
version: 6.0.2
@@ -608,13 +605,13 @@ importers:
version: 11.3.3(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))
vite-plus:
specifier: 0.1.13
- version: 0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
+ version: 0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
vitest:
specifier: npm:@voidzero-dev/vite-plus-test@0.1.13
- version: '@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)'
+ version: '@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)'
vitest-canvas-mock:
specifier: 1.1.3
- version: 1.1.3(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))
+ version: 1.1.3(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))
packages:
@@ -3510,6 +3507,12 @@ packages:
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
+ '@types/whatwg-mimetype@3.0.2':
+ resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==}
+
+ '@types/ws@8.18.1':
+ resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
+
'@types/yauzl@2.10.3':
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
@@ -4082,9 +4085,6 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
- bezier-easing@2.1.0:
- resolution: {integrity: sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==}
-
bidi-js@1.0.3:
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
@@ -4385,9 +4385,6 @@ packages:
resolution: {integrity: sha512-3O5QdqgFRUbXvK1x5INf1YkBz1UKSWqrd63vWsum8MNHDBYD5urm3QtxZbKU259OrEXNM26lP/MPY3d1IGkBgA==}
engines: {node: '>=16'}
- css-mediaquery@0.1.2:
- resolution: {integrity: sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==}
-
css-select@5.2.2:
resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
@@ -5359,6 +5356,10 @@ packages:
hachure-fill@0.5.2:
resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
+ happy-dom@20.8.8:
+ resolution: {integrity: sha512-5/F8wxkNxYtsN0bXfMwIyNLZ9WYsoOYPbmoluqVJqv8KBUbcyKZawJ7uYK4WTX8IHBLYv+VXIwfeNDPy1oKMwQ==}
+ engines: {node: '>=20.0.0'}
+
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@@ -5690,10 +5691,6 @@ packages:
resolution: {integrity: sha512-/2uqY7x6bsrpi3i9LVU6J89352C0rpMk0as8trXxCtvd4kPk1ke/Eyif6wqfSLvoNJqcDG9Vk4UsXgygzCt2xA==}
engines: {node: '>=20.0.0'}
- jsdom-testing-mocks@1.16.0:
- resolution: {integrity: sha512-wLrulXiLpjmcUYOYGEvz4XARkrmdVpyxzdBl9IAMbQ+ib2/UhUTRCn49McdNfXLff2ysGBUms49ZKX0LR1Q0gg==}
- engines: {node: '>=14'}
-
jsdom@29.0.1:
resolution: {integrity: sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0}
@@ -7841,6 +7838,10 @@ packages:
engines: {node: '>=18'}
deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
+ whatwg-mimetype@3.0.0:
+ resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
+ engines: {node: '>=12'}
+
whatwg-mimetype@4.0.0:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
engines: {node: '>=18'}
@@ -8140,7 +8141,7 @@ snapshots:
idb: 8.0.0
tslib: 2.8.1
- '@antfu/eslint-config@7.7.3(@eslint-react/eslint-plugin@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.2.1)(@typescript-eslint/rule-tester@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.1.0(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.1.0(jiti@1.21.7)))(eslint@10.1.0(jiti@1.21.7))(oxlint@1.56.0(oxlint-tsgolint@0.17.1))(typescript@5.9.3)':
+ '@antfu/eslint-config@7.7.3(@eslint-react/eslint-plugin@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.2.1)(@typescript-eslint/rule-tester@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.1.0(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.1.0(jiti@1.21.7)))(eslint@10.1.0(jiti@1.21.7))(oxlint@1.56.0(oxlint-tsgolint@0.17.1))(typescript@5.9.3)':
dependencies:
'@antfu/install-pkg': 1.1.0
'@clack/prompts': 1.1.0
@@ -8150,7 +8151,7 @@ snapshots:
'@stylistic/eslint-plugin': 5.10.0(eslint@10.1.0(jiti@1.21.7))
'@typescript-eslint/eslint-plugin': 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)
'@typescript-eslint/parser': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)
- '@vitest/eslint-plugin': 1.6.12(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)
+ '@vitest/eslint-plugin': 1.6.12(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)
ansis: 4.2.0
cac: 7.0.0
eslint: 10.1.0(jiti@1.21.7)
@@ -8217,6 +8218,7 @@ snapshots:
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
lru-cache: 11.2.7
+ optional: true
'@asamuzakjp/dom-selector@7.0.3':
dependencies:
@@ -8225,8 +8227,10 @@ snapshots:
css-tree: 3.2.1
is-potential-custom-element-name: 1.0.1
lru-cache: 11.2.7
+ optional: true
- '@asamuzakjp/nwsapi@2.3.9': {}
+ '@asamuzakjp/nwsapi@2.3.9':
+ optional: true
'@babel/code-frame@7.29.0':
dependencies:
@@ -8361,6 +8365,7 @@ snapshots:
'@bramus/specificity@2.4.2':
dependencies:
css-tree: 3.2.1
+ optional: true
'@chevrotain/cst-dts-gen@11.1.2':
dependencies:
@@ -8453,12 +8458,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@csstools/color-helpers@6.0.2': {}
+ '@csstools/color-helpers@6.0.2':
+ optional: true
'@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
dependencies:
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
+ optional: true
'@csstools/css-color-parser@4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
dependencies:
@@ -8466,16 +8473,20 @@ snapshots:
'@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
+ optional: true
'@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)':
dependencies:
'@csstools/css-tokenizer': 4.0.0
+ optional: true
'@csstools/css-syntax-patches-for-csstree@1.1.1(css-tree@3.2.1)':
optionalDependencies:
css-tree: 3.2.1
+ optional: true
- '@csstools/css-tokenizer@4.0.0': {}
+ '@csstools/css-tokenizer@4.0.0':
+ optional: true
'@e18e/eslint-plugin@0.2.0(eslint@10.1.0(jiti@1.21.7))(oxlint@1.56.0(oxlint-tsgolint@0.17.1))':
dependencies:
@@ -8777,7 +8788,8 @@ snapshots:
'@eslint/core': 1.1.1
levn: 0.4.1
- '@exodus/bytes@1.15.0': {}
+ '@exodus/bytes@1.15.0':
+ optional: true
'@floating-ui/core@1.7.5':
dependencies:
@@ -10814,6 +10826,12 @@ snapshots:
'@types/unist@3.0.3': {}
+ '@types/whatwg-mimetype@3.0.2': {}
+
+ '@types/ws@8.18.1':
+ dependencies:
+ '@types/node': 25.5.0
+
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 25.5.0
@@ -11019,7 +11037,7 @@ snapshots:
optionalDependencies:
react-server-dom-webpack: 19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))
- '@vitest/coverage-v8@4.1.0(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))':
+ '@vitest/coverage-v8@4.1.0(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))':
dependencies:
'@bcoe/v8-coverage': 1.0.2
'@vitest/utils': 4.1.0
@@ -11031,16 +11049,16 @@ snapshots:
obug: 2.1.1
std-env: 4.0.0
tinyrainbow: 3.1.0
- vitest: '@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)'
+ vitest: '@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)'
- '@vitest/eslint-plugin@1.6.12(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)':
+ '@vitest/eslint-plugin@1.6.12(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.57.1
'@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)
eslint: 10.1.0(jiti@1.21.7)
optionalDependencies:
typescript: 5.9.3
- vitest: '@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)'
+ vitest: '@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)'
transitivePeerDependencies:
- supports-color
@@ -11105,7 +11123,7 @@ snapshots:
'@voidzero-dev/vite-plus-linux-x64-gnu@0.1.13':
optional: true
- '@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)':
+ '@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)':
dependencies:
'@standard-schema/spec': 1.1.0
'@types/chai': 5.2.3
@@ -11123,6 +11141,7 @@ snapshots:
ws: 8.19.0
optionalDependencies:
'@types/node': 25.5.0
+ happy-dom: 20.8.8
jsdom: 29.0.1(canvas@3.2.2)
transitivePeerDependencies:
- '@arethetypeswrong/core'
@@ -11419,11 +11438,10 @@ snapshots:
baseline-browser-mapping@2.10.8: {}
- bezier-easing@2.1.0: {}
-
bidi-js@1.0.3:
dependencies:
require-from-string: 2.0.2
+ optional: true
binary-extensions@2.3.0: {}
@@ -11715,8 +11733,6 @@ snapshots:
css-gradient-parser@0.0.16: {}
- css-mediaquery@0.1.2: {}
-
css-select@5.2.2:
dependencies:
boolbase: 1.0.0
@@ -11745,6 +11761,7 @@ snapshots:
dependencies:
mdn-data: 2.27.1
source-map-js: 1.2.1
+ optional: true
css-what@6.2.2: {}
@@ -11950,6 +11967,7 @@ snapshots:
whatwg-url: 16.0.1
transitivePeerDependencies:
- '@noble/hashes'
+ optional: true
dayjs@1.11.20: {}
@@ -12897,6 +12915,18 @@ snapshots:
hachure-fill@0.5.2: {}
+ happy-dom@20.8.8:
+ dependencies:
+ '@types/node': 25.5.0
+ '@types/whatwg-mimetype': 3.0.2
+ '@types/ws': 8.18.1
+ entities: 7.0.1
+ whatwg-mimetype: 3.0.0
+ ws: 8.19.0
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
has-flag@4.0.0: {}
hast-util-from-dom@5.0.1:
@@ -13061,6 +13091,7 @@ snapshots:
'@exodus/bytes': 1.15.0
transitivePeerDependencies:
- '@noble/hashes'
+ optional: true
html-entities@2.6.0: {}
@@ -13199,7 +13230,8 @@ snapshots:
is-plain-obj@4.1.0: {}
- is-potential-custom-element-name@1.0.1: {}
+ is-potential-custom-element-name@1.0.1:
+ optional: true
is-reference@3.0.3:
dependencies:
@@ -13261,11 +13293,6 @@ snapshots:
jsdoc-type-pratt-parser@7.1.1: {}
- jsdom-testing-mocks@1.16.0:
- dependencies:
- bezier-easing: 2.1.0
- css-mediaquery: 0.1.2
-
jsdom@29.0.1(canvas@3.2.2):
dependencies:
'@asamuzakjp/css-color': 5.0.1
@@ -13293,6 +13320,7 @@ snapshots:
canvas: 3.2.2
transitivePeerDependencies:
- '@noble/hashes'
+ optional: true
jsesc@3.1.0: {}
@@ -13753,7 +13781,8 @@ snapshots:
mdn-data@2.23.0: {}
- mdn-data@2.27.1: {}
+ mdn-data@2.27.1:
+ optional: true
memoize-one@5.2.1: {}
@@ -15121,6 +15150,7 @@ snapshots:
saxes@6.0.0:
dependencies:
xmlchars: 2.2.0
+ optional: true
scheduler@0.27.0: {}
@@ -15397,7 +15427,8 @@ snapshots:
picocolors: 1.1.1
sax: 1.6.0
- symbol-tree@3.2.4: {}
+ symbol-tree@3.2.4:
+ optional: true
synckit@0.11.12:
dependencies:
@@ -15559,10 +15590,12 @@ snapshots:
tough-cookie@6.0.1:
dependencies:
tldts: 7.0.27
+ optional: true
tr46@6.0.0:
dependencies:
punycode: 2.3.1
+ optional: true
trim-lines@3.0.1: {}
@@ -15655,7 +15688,8 @@ snapshots:
undici@7.24.0: {}
- undici@7.24.5: {}
+ undici@7.24.5:
+ optional: true
unicode-trie@2.0.0:
dependencies:
@@ -15879,11 +15913,11 @@ snapshots:
- supports-color
- typescript
- vite-plus@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3):
+ vite-plus@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3):
dependencies:
'@oxc-project/types': 0.120.0
'@voidzero-dev/vite-plus-core': 0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
- '@voidzero-dev/vite-plus-test': 0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
+ '@voidzero-dev/vite-plus-test': 0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
cac: 7.0.0
cross-spawn: 7.0.6
oxfmt: 0.41.0
@@ -15950,11 +15984,11 @@ snapshots:
optionalDependencies:
vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)'
- vitest-canvas-mock@1.1.3(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)):
+ vitest-canvas-mock@1.1.3(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)):
dependencies:
cssfontparser: 1.2.1
moo-color: 1.0.3
- vitest: '@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)'
+ vitest: '@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.8)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)'
void-elements@3.1.0: {}
@@ -15990,6 +16024,7 @@ snapshots:
w3c-xmlserializer@5.0.0:
dependencies:
xml-name-validator: 5.0.0
+ optional: true
walk-up-path@4.0.0: {}
@@ -16002,7 +16037,8 @@ snapshots:
web-vitals@5.1.0: {}
- webidl-conversions@8.0.1: {}
+ webidl-conversions@8.0.1:
+ optional: true
webpack-sources@3.3.4: {}
@@ -16044,9 +16080,12 @@ snapshots:
dependencies:
iconv-lite: 0.6.3
+ whatwg-mimetype@3.0.0: {}
+
whatwg-mimetype@4.0.0: {}
- whatwg-mimetype@5.0.0: {}
+ whatwg-mimetype@5.0.0:
+ optional: true
whatwg-url@16.0.1:
dependencies:
@@ -16055,6 +16094,7 @@ snapshots:
webidl-conversions: 8.0.1
transitivePeerDependencies:
- '@noble/hashes'
+ optional: true
which@2.0.2:
dependencies:
@@ -16078,9 +16118,11 @@ snapshots:
xml-name-validator@4.0.0: {}
- xml-name-validator@5.0.0: {}
+ xml-name-validator@5.0.0:
+ optional: true
- xmlchars@2.2.0: {}
+ xmlchars@2.2.0:
+ optional: true
xtend@4.0.2: {}
diff --git a/web/vite.config.ts b/web/vite.config.ts
index 617cae9ab5..28746f81ca 100644
--- a/web/vite.config.ts
+++ b/web/vite.config.ts
@@ -75,7 +75,8 @@ export default defineConfig(({ mode }) => {
// Vitest config
test: {
- environment: 'jsdom',
+ pool: 'threads',
+ environment: 'happy-dom',
globals: true,
setupFiles: ['./vitest.setup.ts'],
coverage: {
diff --git a/web/vitest.setup.ts b/web/vitest.setup.ts
index e63ea2b54e..ac26ac5d25 100644
--- a/web/vitest.setup.ts
+++ b/web/vitest.setup.ts
@@ -1,14 +1,8 @@
import { act, cleanup } from '@testing-library/react'
-import { mockAnimationsApi, mockResizeObserver } from 'jsdom-testing-mocks'
import * as React from 'react'
import '@testing-library/jest-dom/vitest'
import 'vitest-canvas-mock'
-mockResizeObserver()
-
-// Mock Web Animations API for Headless UI
-mockAnimationsApi()
-
// Suppress act() warnings from @headlessui/react internal Transition component
// These warnings are caused by Headless UI's internal async state updates, not our code
const originalConsoleError = console.error
@@ -77,24 +71,10 @@ if (typeof globalThis.IntersectionObserver === 'undefined') {
}
}
-// Mock Element.scrollIntoView for tests (not available in happy-dom/jsdom)
-if (typeof Element !== 'undefined' && !Element.prototype.scrollIntoView)
- Element.prototype.scrollIntoView = function () { /* noop */ }
-
-// Mock DOMRect.fromRect for tests (not available in jsdom)
-if (typeof DOMRect !== 'undefined' && typeof (DOMRect as typeof DOMRect & { fromRect?: unknown }).fromRect !== 'function') {
- (DOMRect as typeof DOMRect & { fromRect: (rect?: DOMRectInit) => DOMRect }).fromRect = (rect = {}) => new DOMRect(
- rect.x ?? 0,
- rect.y ?? 0,
- rect.width ?? 0,
- rect.height ?? 0,
- )
-}
-
afterEach(async () => {
// Wrap cleanup in act() to flush pending React scheduler work
// This prevents "window is not defined" errors from React 19's scheduler
- // which uses setImmediate/MessageChannel that can fire after jsdom cleanup
+ // which uses setImmediate/MessageChannel that can fire after DOM cleanup
await act(async () => {
cleanup()
})
@@ -131,19 +111,97 @@ vi.mock('@floating-ui/react', async () => {
}
})
-// mock window.matchMedia
-Object.defineProperty(window, 'matchMedia', {
- writable: true,
- value: vi.fn().mockImplementation(query => ({
- matches: false,
- media: query,
- onchange: null,
- addListener: vi.fn(), // deprecated
- removeListener: vi.fn(), // deprecated
- addEventListener: vi.fn(),
- removeEventListener: vi.fn(),
- dispatchEvent: vi.fn(),
- })),
+vi.mock('@monaco-editor/react', () => {
+ const createEditorMock = () => {
+ const focusListeners: Array<() => void> = []
+ const blurListeners: Array<() => void> = []
+
+ return {
+ getContentHeight: vi.fn(() => 56),
+ onDidFocusEditorText: vi.fn((listener: () => void) => {
+ focusListeners.push(listener)
+ return { dispose: vi.fn() }
+ }),
+ onDidBlurEditorText: vi.fn((listener: () => void) => {
+ blurListeners.push(listener)
+ return { dispose: vi.fn() }
+ }),
+ layout: vi.fn(),
+ getAction: vi.fn(() => ({ run: vi.fn() })),
+ getModel: vi.fn(() => ({
+ getLineContent: vi.fn(() => ''),
+ })),
+ getPosition: vi.fn(() => ({ lineNumber: 1, column: 1 })),
+ deltaDecorations: vi.fn(() => []),
+ focus: vi.fn(() => {
+ focusListeners.forEach(listener => listener())
+ }),
+ setPosition: vi.fn(),
+ revealLine: vi.fn(),
+ trigger: vi.fn(),
+ __blur: () => {
+ blurListeners.forEach(listener => listener())
+ },
+ }
+ }
+
+ const monacoMock = {
+ editor: {
+ setTheme: vi.fn(),
+ defineTheme: vi.fn(),
+ },
+ Range: class {
+ startLineNumber: number
+ startColumn: number
+ endLineNumber: number
+ endColumn: number
+ constructor(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number) {
+ this.startLineNumber = startLineNumber
+ this.startColumn = startColumn
+ this.endLineNumber = endLineNumber
+ this.endColumn = endColumn
+ }
+ },
+ }
+
+ const MonacoEditor = ({
+ value = '',
+ onChange,
+ onMount,
+ options,
+ }: {
+ value?: string
+ onChange?: (value: string | undefined) => void
+ onMount?: (editor: ReturnType, monaco: typeof monacoMock) => void
+ options?: { readOnly?: boolean }
+ }) => {
+ const editorRef = React.useRef | null>(null)
+ if (!editorRef.current)
+ editorRef.current = createEditorMock()
+
+ React.useEffect(() => {
+ onMount?.(editorRef.current!, monacoMock)
+ }, [onMount])
+
+ return React.createElement('textarea', {
+ 'data-testid': 'monaco-editor',
+ 'readOnly': options?.readOnly,
+ value,
+ 'onChange': (event: React.ChangeEvent) => onChange?.(event.target.value),
+ 'onFocus': () => editorRef.current?.focus(),
+ 'onBlur': () => editorRef.current?.__blur(),
+ })
+ }
+
+ return {
+ __esModule: true,
+ default: MonacoEditor,
+ Editor: MonacoEditor,
+ loader: {
+ config: vi.fn(),
+ init: vi.fn().mockResolvedValue(monacoMock),
+ },
+ }
})
// Mock localStorage for testing