mirror of
https://github.com/langgenius/dify.git
synced 2026-04-05 19:21:05 +08:00
test: use happy dom (#34154)
This commit is contained in:
@@ -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<typeof createEditorMock>, monaco: typeof monacoMock) => void
|
||||
options?: { readOnly?: boolean }
|
||||
}) => {
|
||||
const editorRef = React.useRef<ReturnType<typeof createEditorMock> | 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<HTMLTextAreaElement>) => 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
|
||||
|
||||
Reference in New Issue
Block a user