mirror of
https://github.com/langgenius/dify.git
synced 2026-05-03 08:58:09 +08:00
test: add integration tests for app card operations, list browsing, and create app flows (#32298)
Co-authored-by: CodingOnStar <hanxujiang@dify.com>
This commit is contained in:
@ -0,0 +1,233 @@
|
||||
import type { UrlUpdateEvent } from 'nuqs/adapters/testing'
|
||||
import type { ReactNode } from 'react'
|
||||
import { act, renderHook, waitFor } from '@testing-library/react'
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
|
||||
import useAppsQueryState from '../use-apps-query-state'
|
||||
|
||||
const renderWithAdapter = (searchParams = '') => {
|
||||
const onUrlUpdate = vi.fn<(event: UrlUpdateEvent) => void>()
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<NuqsTestingAdapter searchParams={searchParams} onUrlUpdate={onUrlUpdate}>
|
||||
{children}
|
||||
</NuqsTestingAdapter>
|
||||
)
|
||||
const { result } = renderHook(() => useAppsQueryState(), { wrapper })
|
||||
return { result, onUrlUpdate }
|
||||
}
|
||||
|
||||
describe('useAppsQueryState', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Initialization', () => {
|
||||
it('should expose query and setQuery when initialized', () => {
|
||||
const { result } = renderWithAdapter()
|
||||
|
||||
expect(result.current.query).toBeDefined()
|
||||
expect(typeof result.current.setQuery).toBe('function')
|
||||
})
|
||||
|
||||
it('should default to empty filters when search params are missing', () => {
|
||||
const { result } = renderWithAdapter()
|
||||
|
||||
expect(result.current.query.tagIDs).toBeUndefined()
|
||||
expect(result.current.query.keywords).toBeUndefined()
|
||||
expect(result.current.query.isCreatedByMe).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Parsing search params', () => {
|
||||
it('should parse tagIDs when URL includes tagIDs', () => {
|
||||
const { result } = renderWithAdapter('?tagIDs=tag1;tag2;tag3')
|
||||
|
||||
expect(result.current.query.tagIDs).toEqual(['tag1', 'tag2', 'tag3'])
|
||||
})
|
||||
|
||||
it('should parse keywords when URL includes keywords', () => {
|
||||
const { result } = renderWithAdapter('?keywords=search+term')
|
||||
|
||||
expect(result.current.query.keywords).toBe('search term')
|
||||
})
|
||||
|
||||
it('should parse isCreatedByMe when URL includes true value', () => {
|
||||
const { result } = renderWithAdapter('?isCreatedByMe=true')
|
||||
|
||||
expect(result.current.query.isCreatedByMe).toBe(true)
|
||||
})
|
||||
|
||||
it('should parse all params when URL includes multiple filters', () => {
|
||||
const { result } = renderWithAdapter(
|
||||
'?tagIDs=tag1;tag2&keywords=test&isCreatedByMe=true',
|
||||
)
|
||||
|
||||
expect(result.current.query.tagIDs).toEqual(['tag1', 'tag2'])
|
||||
expect(result.current.query.keywords).toBe('test')
|
||||
expect(result.current.query.isCreatedByMe).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Updating query state', () => {
|
||||
it('should update keywords when setQuery receives keywords', () => {
|
||||
const { result } = renderWithAdapter()
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery({ keywords: 'new search' })
|
||||
})
|
||||
|
||||
expect(result.current.query.keywords).toBe('new search')
|
||||
})
|
||||
|
||||
it('should update tagIDs when setQuery receives tagIDs', () => {
|
||||
const { result } = renderWithAdapter()
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery({ tagIDs: ['tag1', 'tag2'] })
|
||||
})
|
||||
|
||||
expect(result.current.query.tagIDs).toEqual(['tag1', 'tag2'])
|
||||
})
|
||||
|
||||
it('should update isCreatedByMe when setQuery receives true', () => {
|
||||
const { result } = renderWithAdapter()
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery({ isCreatedByMe: true })
|
||||
})
|
||||
|
||||
expect(result.current.query.isCreatedByMe).toBe(true)
|
||||
})
|
||||
|
||||
it('should support partial updates when setQuery uses callback', () => {
|
||||
const { result } = renderWithAdapter()
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery({ keywords: 'initial' })
|
||||
})
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery(prev => ({ ...prev, isCreatedByMe: true }))
|
||||
})
|
||||
|
||||
expect(result.current.query.keywords).toBe('initial')
|
||||
expect(result.current.query.isCreatedByMe).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('URL synchronization', () => {
|
||||
it('should sync keywords to URL when keywords change', async () => {
|
||||
const { result, onUrlUpdate } = renderWithAdapter()
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery({ keywords: 'search' })
|
||||
})
|
||||
|
||||
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
|
||||
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
|
||||
expect(update.searchParams.get('keywords')).toBe('search')
|
||||
expect(update.options.history).toBe('push')
|
||||
})
|
||||
|
||||
it('should sync tagIDs to URL when tagIDs change', async () => {
|
||||
const { result, onUrlUpdate } = renderWithAdapter()
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery({ tagIDs: ['tag1', 'tag2'] })
|
||||
})
|
||||
|
||||
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
|
||||
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
|
||||
expect(update.searchParams.get('tagIDs')).toBe('tag1;tag2')
|
||||
})
|
||||
|
||||
it('should sync isCreatedByMe to URL when enabled', async () => {
|
||||
const { result, onUrlUpdate } = renderWithAdapter()
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery({ isCreatedByMe: true })
|
||||
})
|
||||
|
||||
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
|
||||
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
|
||||
expect(update.searchParams.get('isCreatedByMe')).toBe('true')
|
||||
})
|
||||
|
||||
it('should remove keywords from URL when keywords are cleared', async () => {
|
||||
const { result, onUrlUpdate } = renderWithAdapter('?keywords=existing')
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery({ keywords: '' })
|
||||
})
|
||||
|
||||
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
|
||||
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
|
||||
expect(update.searchParams.has('keywords')).toBe(false)
|
||||
})
|
||||
|
||||
it('should remove tagIDs from URL when tagIDs are empty', async () => {
|
||||
const { result, onUrlUpdate } = renderWithAdapter('?tagIDs=tag1;tag2')
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery({ tagIDs: [] })
|
||||
})
|
||||
|
||||
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
|
||||
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
|
||||
expect(update.searchParams.has('tagIDs')).toBe(false)
|
||||
})
|
||||
|
||||
it('should remove isCreatedByMe from URL when disabled', async () => {
|
||||
const { result, onUrlUpdate } = renderWithAdapter('?isCreatedByMe=true')
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery({ isCreatedByMe: false })
|
||||
})
|
||||
|
||||
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
|
||||
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
|
||||
expect(update.searchParams.has('isCreatedByMe')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should treat empty tagIDs as empty list when URL param is empty', () => {
|
||||
const { result } = renderWithAdapter('?tagIDs=')
|
||||
|
||||
expect(result.current.query.tagIDs).toEqual([])
|
||||
})
|
||||
|
||||
it('should treat empty keywords as undefined when URL param is empty', () => {
|
||||
const { result } = renderWithAdapter('?keywords=')
|
||||
|
||||
expect(result.current.query.keywords).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should decode keywords with spaces when URL contains encoded spaces', () => {
|
||||
const { result } = renderWithAdapter('?keywords=test+with+spaces')
|
||||
|
||||
expect(result.current.query.keywords).toBe('test with spaces')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Integration scenarios', () => {
|
||||
it('should keep accumulated filters when updates are sequential', () => {
|
||||
const { result } = renderWithAdapter()
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery({ keywords: 'first' })
|
||||
})
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery(prev => ({ ...prev, tagIDs: ['tag1'] }))
|
||||
})
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery(prev => ({ ...prev, isCreatedByMe: true }))
|
||||
})
|
||||
|
||||
expect(result.current.query.keywords).toBe('first')
|
||||
expect(result.current.query.tagIDs).toEqual(['tag1'])
|
||||
expect(result.current.query.isCreatedByMe).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,475 @@
|
||||
import type { Mock } from 'vitest'
|
||||
import { act, renderHook } from '@testing-library/react'
|
||||
import { useDSLDragDrop } from '../use-dsl-drag-drop'
|
||||
|
||||
describe('useDSLDragDrop', () => {
|
||||
let container: HTMLDivElement
|
||||
let mockOnDSLFileDropped: Mock
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
mockOnDSLFileDropped = vi.fn()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(container)
|
||||
})
|
||||
|
||||
const createDragEvent = (type: string, files: File[] = []) => {
|
||||
const dataTransfer = {
|
||||
types: files.length > 0 ? ['Files'] : [],
|
||||
files,
|
||||
}
|
||||
|
||||
const event = new Event(type, { bubbles: true, cancelable: true }) as DragEvent
|
||||
Object.defineProperty(event, 'dataTransfer', {
|
||||
value: dataTransfer,
|
||||
writable: false,
|
||||
})
|
||||
Object.defineProperty(event, 'preventDefault', {
|
||||
value: vi.fn(),
|
||||
writable: false,
|
||||
})
|
||||
Object.defineProperty(event, 'stopPropagation', {
|
||||
value: vi.fn(),
|
||||
writable: false,
|
||||
})
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
const createMockFile = (name: string) => {
|
||||
return new File(['content'], name, { type: 'application/x-yaml' })
|
||||
}
|
||||
|
||||
describe('Basic functionality', () => {
|
||||
it('should return dragging state', () => {
|
||||
const containerRef = { current: container }
|
||||
const { result } = renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
expect(result.current.dragging).toBe(false)
|
||||
})
|
||||
|
||||
it('should initialize with dragging as false', () => {
|
||||
const containerRef = { current: container }
|
||||
const { result } = renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
expect(result.current.dragging).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Drag events', () => {
|
||||
it('should set dragging to true on dragenter with files', () => {
|
||||
const containerRef = { current: container }
|
||||
const { result } = renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
const file = createMockFile('test.yaml')
|
||||
const event = createDragEvent('dragenter', [file])
|
||||
|
||||
act(() => {
|
||||
container.dispatchEvent(event)
|
||||
})
|
||||
|
||||
expect(result.current.dragging).toBe(true)
|
||||
})
|
||||
|
||||
it('should not set dragging on dragenter without files', () => {
|
||||
const containerRef = { current: container }
|
||||
const { result } = renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
const event = createDragEvent('dragenter', [])
|
||||
|
||||
act(() => {
|
||||
container.dispatchEvent(event)
|
||||
})
|
||||
|
||||
expect(result.current.dragging).toBe(false)
|
||||
})
|
||||
|
||||
it('should handle dragover event', () => {
|
||||
const containerRef = { current: container }
|
||||
renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
const event = createDragEvent('dragover')
|
||||
|
||||
act(() => {
|
||||
container.dispatchEvent(event)
|
||||
})
|
||||
|
||||
expect(event.preventDefault).toHaveBeenCalled()
|
||||
expect(event.stopPropagation).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should set dragging to false on dragleave when leaving container', () => {
|
||||
const containerRef = { current: container }
|
||||
const { result } = renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
const enterEvent = createDragEvent('dragenter', [createMockFile('test.yaml')])
|
||||
act(() => {
|
||||
container.dispatchEvent(enterEvent)
|
||||
})
|
||||
expect(result.current.dragging).toBe(true)
|
||||
|
||||
const leaveEvent = createDragEvent('dragleave')
|
||||
Object.defineProperty(leaveEvent, 'relatedTarget', {
|
||||
value: null,
|
||||
writable: false,
|
||||
})
|
||||
|
||||
act(() => {
|
||||
container.dispatchEvent(leaveEvent)
|
||||
})
|
||||
|
||||
expect(result.current.dragging).toBe(false)
|
||||
})
|
||||
|
||||
it('should not set dragging to false on dragleave when within container', () => {
|
||||
const containerRef = { current: container }
|
||||
const childElement = document.createElement('div')
|
||||
container.appendChild(childElement)
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
const enterEvent = createDragEvent('dragenter', [createMockFile('test.yaml')])
|
||||
act(() => {
|
||||
container.dispatchEvent(enterEvent)
|
||||
})
|
||||
expect(result.current.dragging).toBe(true)
|
||||
|
||||
const leaveEvent = createDragEvent('dragleave')
|
||||
Object.defineProperty(leaveEvent, 'relatedTarget', {
|
||||
value: childElement,
|
||||
writable: false,
|
||||
})
|
||||
|
||||
act(() => {
|
||||
container.dispatchEvent(leaveEvent)
|
||||
})
|
||||
|
||||
expect(result.current.dragging).toBe(true)
|
||||
|
||||
container.removeChild(childElement)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Drop functionality', () => {
|
||||
it('should call onDSLFileDropped for .yaml file', () => {
|
||||
const containerRef = { current: container }
|
||||
renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
const file = createMockFile('test.yaml')
|
||||
const dropEvent = createDragEvent('drop', [file])
|
||||
|
||||
act(() => {
|
||||
container.dispatchEvent(dropEvent)
|
||||
})
|
||||
|
||||
expect(mockOnDSLFileDropped).toHaveBeenCalledWith(file)
|
||||
})
|
||||
|
||||
it('should call onDSLFileDropped for .yml file', () => {
|
||||
const containerRef = { current: container }
|
||||
renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
const file = createMockFile('test.yml')
|
||||
const dropEvent = createDragEvent('drop', [file])
|
||||
|
||||
act(() => {
|
||||
container.dispatchEvent(dropEvent)
|
||||
})
|
||||
|
||||
expect(mockOnDSLFileDropped).toHaveBeenCalledWith(file)
|
||||
})
|
||||
|
||||
it('should call onDSLFileDropped for uppercase .YAML file', () => {
|
||||
const containerRef = { current: container }
|
||||
renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
const file = createMockFile('test.YAML')
|
||||
const dropEvent = createDragEvent('drop', [file])
|
||||
|
||||
act(() => {
|
||||
container.dispatchEvent(dropEvent)
|
||||
})
|
||||
|
||||
expect(mockOnDSLFileDropped).toHaveBeenCalledWith(file)
|
||||
})
|
||||
|
||||
it('should not call onDSLFileDropped for non-yaml file', () => {
|
||||
const containerRef = { current: container }
|
||||
renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
const file = createMockFile('test.json')
|
||||
const dropEvent = createDragEvent('drop', [file])
|
||||
|
||||
act(() => {
|
||||
container.dispatchEvent(dropEvent)
|
||||
})
|
||||
|
||||
expect(mockOnDSLFileDropped).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should set dragging to false on drop', () => {
|
||||
const containerRef = { current: container }
|
||||
const { result } = renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
const enterEvent = createDragEvent('dragenter', [createMockFile('test.yaml')])
|
||||
act(() => {
|
||||
container.dispatchEvent(enterEvent)
|
||||
})
|
||||
expect(result.current.dragging).toBe(true)
|
||||
|
||||
const dropEvent = createDragEvent('drop', [createMockFile('test.yaml')])
|
||||
act(() => {
|
||||
container.dispatchEvent(dropEvent)
|
||||
})
|
||||
|
||||
expect(result.current.dragging).toBe(false)
|
||||
})
|
||||
|
||||
it('should handle drop with no dataTransfer', () => {
|
||||
const containerRef = { current: container }
|
||||
renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
const event = new Event('drop', { bubbles: true, cancelable: true }) as DragEvent
|
||||
Object.defineProperty(event, 'dataTransfer', {
|
||||
value: null,
|
||||
writable: false,
|
||||
})
|
||||
Object.defineProperty(event, 'preventDefault', {
|
||||
value: vi.fn(),
|
||||
writable: false,
|
||||
})
|
||||
Object.defineProperty(event, 'stopPropagation', {
|
||||
value: vi.fn(),
|
||||
writable: false,
|
||||
})
|
||||
|
||||
act(() => {
|
||||
container.dispatchEvent(event)
|
||||
})
|
||||
|
||||
expect(mockOnDSLFileDropped).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle drop with empty files array', () => {
|
||||
const containerRef = { current: container }
|
||||
renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
const dropEvent = createDragEvent('drop', [])
|
||||
|
||||
act(() => {
|
||||
container.dispatchEvent(dropEvent)
|
||||
})
|
||||
|
||||
expect(mockOnDSLFileDropped).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should only process the first file when multiple files are dropped', () => {
|
||||
const containerRef = { current: container }
|
||||
renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
const file1 = createMockFile('test1.yaml')
|
||||
const file2 = createMockFile('test2.yaml')
|
||||
const dropEvent = createDragEvent('drop', [file1, file2])
|
||||
|
||||
act(() => {
|
||||
container.dispatchEvent(dropEvent)
|
||||
})
|
||||
|
||||
expect(mockOnDSLFileDropped).toHaveBeenCalledTimes(1)
|
||||
expect(mockOnDSLFileDropped).toHaveBeenCalledWith(file1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Enabled prop', () => {
|
||||
it('should not add event listeners when enabled is false', () => {
|
||||
const containerRef = { current: container }
|
||||
const { result } = renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
enabled: false,
|
||||
}),
|
||||
)
|
||||
|
||||
const file = createMockFile('test.yaml')
|
||||
const enterEvent = createDragEvent('dragenter', [file])
|
||||
|
||||
act(() => {
|
||||
container.dispatchEvent(enterEvent)
|
||||
})
|
||||
|
||||
expect(result.current.dragging).toBe(false)
|
||||
})
|
||||
|
||||
it('should return dragging as false when enabled is false even if state is true', () => {
|
||||
const containerRef = { current: container }
|
||||
const { result, rerender } = renderHook(
|
||||
({ enabled }) =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
enabled,
|
||||
}),
|
||||
{ initialProps: { enabled: true } },
|
||||
)
|
||||
|
||||
const enterEvent = createDragEvent('dragenter', [createMockFile('test.yaml')])
|
||||
act(() => {
|
||||
container.dispatchEvent(enterEvent)
|
||||
})
|
||||
expect(result.current.dragging).toBe(true)
|
||||
|
||||
rerender({ enabled: false })
|
||||
expect(result.current.dragging).toBe(false)
|
||||
})
|
||||
|
||||
it('should default enabled to true', () => {
|
||||
const containerRef = { current: container }
|
||||
const { result } = renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
const enterEvent = createDragEvent('dragenter', [createMockFile('test.yaml')])
|
||||
|
||||
act(() => {
|
||||
container.dispatchEvent(enterEvent)
|
||||
})
|
||||
|
||||
expect(result.current.dragging).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Cleanup', () => {
|
||||
it('should remove event listeners on unmount', () => {
|
||||
const containerRef = { current: container }
|
||||
const removeEventListenerSpy = vi.spyOn(container, 'removeEventListener')
|
||||
|
||||
const { unmount } = renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
unmount()
|
||||
|
||||
expect(removeEventListenerSpy).toHaveBeenCalledWith('dragenter', expect.any(Function))
|
||||
expect(removeEventListenerSpy).toHaveBeenCalledWith('dragover', expect.any(Function))
|
||||
expect(removeEventListenerSpy).toHaveBeenCalledWith('dragleave', expect.any(Function))
|
||||
expect(removeEventListenerSpy).toHaveBeenCalledWith('drop', expect.any(Function))
|
||||
|
||||
removeEventListenerSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should handle null containerRef', () => {
|
||||
const containerRef = { current: null }
|
||||
const { result } = renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
expect(result.current.dragging).toBe(false)
|
||||
})
|
||||
|
||||
it('should handle containerRef changing to null', () => {
|
||||
const containerRef = { current: container as HTMLDivElement | null }
|
||||
const { result, rerender } = renderHook(() =>
|
||||
useDSLDragDrop({
|
||||
onDSLFileDropped: mockOnDSLFileDropped,
|
||||
containerRef,
|
||||
}),
|
||||
)
|
||||
|
||||
containerRef.current = null
|
||||
rerender()
|
||||
|
||||
expect(result.current.dragging).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user