import { act, renderHook } from "@testing-library/react" import { afterEach, beforeEach, describe, expect, it, vi } from "vitest" import { DraggableItem } from "../../services/drag-drop-manager" import { useDragDropState, useDraggable, useDropZone } from "../use-drag-drop" // Mock the drag-drop manager const mockManager = { startDrag: vi.fn(), registerDropTarget: vi.fn(), on: vi.fn(), off: vi.fn(), } vi.mock("../../services/drag-drop-manager", () => ({ getDragDropManager: vi.fn(() => mockManager), DragDropManager: { getInstance: vi.fn(() => mockManager), }, })) // Mock DOM APIs for SSR compatibility const originalWindow = global.window describe("useDraggable", () => { const mockGetData = vi.fn(() => ({ id: "test-item", name: "Test Item" })) const mockGetPreview = vi.fn(() => ({ url: "test-preview.jpg", width: 100, height: 100 })) beforeEach(() => { vi.clearAllMocks() // Ensure window is available for most tests if (!global.window) { Object.defineProperty(global, "window", { value: { addEventListener: vi.fn(), removeEventListener: vi.fn() }, writable: true, configurable: true, }) } }) afterEach(() => { vi.clearAllMocks() // Restore original window state if (originalWindow) { global.window = originalWindow } }) it("should return draggable props for browser environment", () => { const { result } = renderHook(() => useDraggable("media", mockGetData, mockGetPreview)) expect(result.current.draggable).toBe(true) expect(typeof result.current.onDragStart).toBe("function") expect(typeof result.current.onDragEnd).toBe("function") }) it.skip("should return false for draggable in SSR environment", () => { // Skip SSR tests due to React DOM dependencies }) it("should call manager.startDrag when onDragStart is triggered", () => { const { result } = renderHook(() => useDraggable("media", mockGetData, mockGetPreview)) const mockNativeEvent = { clientX: 100, clientY: 200, dataTransfer: { setData: vi.fn(), effectAllowed: "", setDragImage: vi.fn(), }, } as unknown as DragEvent const mockEvent = { nativeEvent: mockNativeEvent, } as React.DragEvent act(() => { result.current.onDragStart(mockEvent) }) expect(mockManager.startDrag).toHaveBeenCalledWith( { type: "media", data: { id: "test-item", name: "Test Item" }, preview: { url: "test-preview.jpg", width: 100, height: 100 }, }, mockNativeEvent, ) expect(mockGetData).toHaveBeenCalled() expect(mockGetPreview).toHaveBeenCalled() }) it("should handle drag start without preview", () => { const { result } = renderHook(() => useDraggable("effect", mockGetData)) const mockNativeEvent = { clientX: 100, clientY: 200, dataTransfer: { setData: vi.fn(), effectAllowed: "", setDragImage: vi.fn(), }, } as unknown as DragEvent const mockEvent = { nativeEvent: mockNativeEvent, } as React.DragEvent act(() => { result.current.onDragStart(mockEvent) }) expect(mockManager.startDrag).toHaveBeenCalledWith( { type: "effect", data: { id: "test-item", name: "Test Item" }, preview: undefined, }, mockNativeEvent, ) }) it.skip("should skip drag start in SSR environment", () => { // Skip SSR tests due to React DOM dependencies }) it("should handle different draggable types", () => { const types: Array = [ "media", "music", "effect", "filter", "transition", "template", "style-template", "subtitle-style", ] types.forEach((type) => { const { result } = renderHook(() => useDraggable(type, mockGetData)) const mockEvent = { nativeEvent: { clientX: 100, clientY: 200, dataTransfer: { setData: vi.fn(), effectAllowed: "", setDragImage: vi.fn() }, } as unknown as DragEvent, } as React.DragEvent act(() => { result.current.onDragStart(mockEvent) }) expect(mockManager.startDrag).toHaveBeenCalledWith( expect.objectContaining({ type, data: { id: "test-item", name: "Test Item" }, }), expect.any(Object), ) vi.clearAllMocks() }) }) }) describe("useDropZone", () => { const mockOnDrop = vi.fn() const mockUnregister = vi.fn() // Mock element with classList const mockClassList = { add: vi.fn(), remove: vi.fn(), } const mockElement = { classList: mockClassList, } beforeEach(() => { vi.clearAllMocks() mockManager.registerDropTarget.mockReturnValue(mockUnregister) // Ensure window is available if (!global.window) { Object.defineProperty(global, "window", { value: { addEventListener: vi.fn(), removeEventListener: vi.fn() }, writable: true, configurable: true, }) } }) afterEach(() => { vi.clearAllMocks() }) it("should register drop target and return handlers", () => { const { result } = renderHook(() => useDropZone("test-zone", ["media", "effect"], mockOnDrop)) expect(result.current.ref).toBeDefined() expect(typeof result.current.onDragOver).toBe("function") expect(typeof result.current.onDrop).toBe("function") }) it.skip("should skip registration in SSR environment", () => { // Skip SSR tests due to React DOM dependencies }) it("should prevent default on drag over", () => { const { result } = renderHook(() => useDropZone("test-zone", ["media"], mockOnDrop)) const mockEvent = { preventDefault: vi.fn(), dataTransfer: { dropEffect: "" }, } as unknown as React.DragEvent act(() => { result.current.onDragOver(mockEvent) }) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(mockEvent.dataTransfer.dropEffect).toBe("copy") }) it("should prevent default on drop", () => { const { result } = renderHook(() => useDropZone("test-zone", ["media"], mockOnDrop)) const mockEvent = { preventDefault: vi.fn(), } as unknown as React.DragEvent act(() => { result.current.onDrop(mockEvent) }) expect(mockEvent.preventDefault).toHaveBeenCalled() }) it("should handle different accepted types", () => { const acceptedTypes: Array> = [["media"], ["effect", "filter"], ["template", "style-template"]] acceptedTypes.forEach((accepts) => { const { result } = renderHook(() => useDropZone("test-zone", accepts as any, mockOnDrop)) expect(result.current.ref).toBeDefined() expect(typeof result.current.onDragOver).toBe("function") expect(typeof result.current.onDrop).toBe("function") vi.clearAllMocks() }) }) }) describe("useDragDropState", () => { beforeEach(() => { vi.clearAllMocks() // Ensure window is available if (!global.window) { Object.defineProperty(global, "window", { value: { addEventListener: vi.fn(), removeEventListener: vi.fn() }, writable: true, configurable: true, }) } }) afterEach(() => { vi.clearAllMocks() }) it("should return null initially", () => { const { result } = renderHook(() => useDragDropState()) expect(result.current).toBeNull() }) it.skip("should skip setup in SSR environment", () => { // Skip SSR tests due to React DOM dependencies }) it("should register event listeners", () => { renderHook(() => useDragDropState()) expect(mockManager.on).toHaveBeenCalledWith("dragStart", expect.any(Function)) expect(mockManager.on).toHaveBeenCalledWith("dragEnd", expect.any(Function)) expect(mockManager.on).toHaveBeenCalledWith("dragCancel", expect.any(Function)) }) it("should update state on drag start", () => { const { result } = renderHook(() => useDragDropState()) // Get the dragStart handler const dragStartCall = mockManager.on.mock.calls.find((call) => call[0] === "dragStart") const dragStartHandler = dragStartCall?.[1] const testItem: DraggableItem = { type: "media", data: { id: "test", name: "Test" }, } act(() => { dragStartHandler?.(testItem) }) expect(result.current).toEqual(testItem) }) it("should clear state on drag end", () => { const { result } = renderHook(() => useDragDropState()) // Get handlers const dragStartCall = mockManager.on.mock.calls.find((call) => call[0] === "dragStart") const dragEndCall = mockManager.on.mock.calls.find((call) => call[0] === "dragEnd") const dragStartHandler = dragStartCall?.[1] const dragEndHandler = dragEndCall?.[1] // Set initial state act(() => { dragStartHandler?.({ type: "media", data: {} }) }) expect(result.current).not.toBeNull() // Clear state act(() => { dragEndHandler?.() }) expect(result.current).toBeNull() }) it("should clear state on drag cancel", () => { const { result } = renderHook(() => useDragDropState()) // Get handlers const dragStartCall = mockManager.on.mock.calls.find((call) => call[0] === "dragStart") const dragCancelCall = mockManager.on.mock.calls.find((call) => call[0] === "dragCancel") const dragStartHandler = dragStartCall?.[1] const dragCancelHandler = dragCancelCall?.[1] // Set initial state act(() => { dragStartHandler?.({ type: "media", data: {} }) }) expect(result.current).not.toBeNull() // Cancel drag act(() => { dragCancelHandler?.() }) expect(result.current).toBeNull() }) it("should unregister event listeners on unmount", () => { const { unmount } = renderHook(() => useDragDropState()) unmount() expect(mockManager.off).toHaveBeenCalledWith("dragStart", expect.any(Function)) expect(mockManager.off).toHaveBeenCalledWith("dragEnd", expect.any(Function)) expect(mockManager.off).toHaveBeenCalledWith("dragCancel", expect.any(Function)) }) it("should maintain state consistency across multiple events", () => { const { result } = renderHook(() => useDragDropState()) // Get handlers const dragStartCall = mockManager.on.mock.calls.find((call) => call[0] === "dragStart") const dragEndCall = mockManager.on.mock.calls.find((call) => call[0] === "dragEnd") const dragCancelCall = mockManager.on.mock.calls.find((call) => call[0] === "dragCancel") const dragStartHandler = dragStartCall?.[1] const dragEndHandler = dragEndCall?.[1] const dragCancelHandler = dragCancelCall?.[1] const testItem1: DraggableItem = { type: "media", data: { id: "test1" } } const testItem2: DraggableItem = { type: "effect", data: { id: "test2" } } // First drag operation act(() => { dragStartHandler?.(testItem1) }) expect(result.current).toEqual(testItem1) act(() => { dragEndHandler?.() }) expect(result.current).toBeNull() // Second drag operation act(() => { dragStartHandler?.(testItem2) }) expect(result.current).toEqual(testItem2) act(() => { dragCancelHandler?.() }) expect(result.current).toBeNull() }) it("should handle rapid state changes", () => { const { result } = renderHook(() => useDragDropState()) // Get handlers const dragStartCall = mockManager.on.mock.calls.find((call) => call[0] === "dragStart") const dragEndCall = mockManager.on.mock.calls.find((call) => call[0] === "dragEnd") const dragStartHandler = dragStartCall?.[1] const dragEndHandler = dragEndCall?.[1] const testItems: DraggableItem[] = [ { type: "media", data: { id: "test1" } }, { type: "effect", data: { id: "test2" } }, { type: "filter", data: { id: "test3" } }, ] // Rapid drag operations testItems.forEach((item, _index) => { act(() => { dragStartHandler?.(item) }) expect(result.current).toEqual(item) act(() => { dragEndHandler?.() }) expect(result.current).toBeNull() }) }) })