import type { FileEntity } from '../types' import type { FileUpload } from '@/app/components/base/features/types' import type { FileUploadConfigResponse } from '@/models/common' import { act, renderHook } from '@testing-library/react' import { useFile, useFileSizeLimit } from '../hooks' const mockNotify = vi.fn() vi.mock('@/next/navigation', () => ({ useParams: () => ({ token: undefined }), })) vi.mock('@/app/components/base/ui/toast', () => ({ toast: { error: (message: string) => mockNotify({ type: 'error', message }), }, })) const mockSetFiles = vi.fn() let mockStoreFiles: FileEntity[] = [] vi.mock('../store', () => ({ useFileStore: () => ({ getState: () => ({ files: mockStoreFiles, setFiles: mockSetFiles, }), }), })) const mockFileUpload = vi.fn() const mockIsAllowedFileExtension = vi.fn().mockReturnValue(true) const mockGetSupportFileType = vi.fn().mockReturnValue('document') vi.mock('../utils', () => ({ fileUpload: (...args: unknown[]) => mockFileUpload(...args), getFileUploadErrorMessage: vi.fn().mockReturnValue('Upload error'), getSupportFileType: (...args: unknown[]) => mockGetSupportFileType(...args), isAllowedFileExtension: (...args: unknown[]) => mockIsAllowedFileExtension(...args), })) const mockUploadRemoteFileInfo = vi.fn() vi.mock('@/service/common', () => ({ uploadRemoteFileInfo: (...args: unknown[]) => mockUploadRemoteFileInfo(...args), })) vi.mock('uuid', () => ({ v4: () => 'mock-uuid', })) describe('useFileSizeLimit', () => { beforeEach(() => { vi.clearAllMocks() }) it('should return default limits when no config is provided', () => { const { result } = renderHook(() => useFileSizeLimit()) expect(result.current.imgSizeLimit).toBe(10 * 1024 * 1024) expect(result.current.docSizeLimit).toBe(15 * 1024 * 1024) expect(result.current.audioSizeLimit).toBe(50 * 1024 * 1024) expect(result.current.videoSizeLimit).toBe(100 * 1024 * 1024) expect(result.current.maxFileUploadLimit).toBe(10) }) it('should use config values when provided', () => { const config: FileUploadConfigResponse = { image_file_size_limit: 20, file_size_limit: 30, audio_file_size_limit: 100, video_file_size_limit: 200, workflow_file_upload_limit: 20, } as FileUploadConfigResponse const { result } = renderHook(() => useFileSizeLimit(config)) expect(result.current.imgSizeLimit).toBe(20 * 1024 * 1024) expect(result.current.docSizeLimit).toBe(30 * 1024 * 1024) expect(result.current.audioSizeLimit).toBe(100 * 1024 * 1024) expect(result.current.videoSizeLimit).toBe(200 * 1024 * 1024) expect(result.current.maxFileUploadLimit).toBe(20) }) it('should fall back to defaults when config values are zero', () => { const config = { image_file_size_limit: 0, file_size_limit: 0, audio_file_size_limit: 0, video_file_size_limit: 0, workflow_file_upload_limit: 0, } as FileUploadConfigResponse const { result } = renderHook(() => useFileSizeLimit(config)) expect(result.current.imgSizeLimit).toBe(10 * 1024 * 1024) expect(result.current.docSizeLimit).toBe(15 * 1024 * 1024) expect(result.current.audioSizeLimit).toBe(50 * 1024 * 1024) expect(result.current.videoSizeLimit).toBe(100 * 1024 * 1024) expect(result.current.maxFileUploadLimit).toBe(10) }) }) describe('useFile', () => { const defaultFileConfig: FileUpload = { enabled: true, allowed_file_types: ['image', 'document'], allowed_file_extensions: [], number_limits: 5, } as FileUpload beforeEach(() => { vi.clearAllMocks() mockStoreFiles = [] mockIsAllowedFileExtension.mockReturnValue(true) mockGetSupportFileType.mockReturnValue('document') }) it('should return all file handler functions', () => { const { result } = renderHook(() => useFile(defaultFileConfig)) expect(result.current.handleAddFile).toBeDefined() expect(result.current.handleUpdateFile).toBeDefined() expect(result.current.handleRemoveFile).toBeDefined() expect(result.current.handleReUploadFile).toBeDefined() expect(result.current.handleLoadFileFromLink).toBeDefined() expect(result.current.handleLoadFileFromLinkSuccess).toBeDefined() expect(result.current.handleLoadFileFromLinkError).toBeDefined() expect(result.current.handleClearFiles).toBeDefined() expect(result.current.handleLocalFileUpload).toBeDefined() expect(result.current.handleClipboardPasteFile).toBeDefined() expect(result.current.handleDragFileEnter).toBeDefined() expect(result.current.handleDragFileOver).toBeDefined() expect(result.current.handleDragFileLeave).toBeDefined() expect(result.current.handleDropFile).toBeDefined() expect(result.current.isDragActive).toBe(false) }) it('should add a file via handleAddFile', () => { const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleAddFile({ id: 'test-id', name: 'test.txt', type: 'text/plain', size: 100, progress: 0, transferMethod: 'local_file', supportFileType: 'document', } as FileEntity) expect(mockSetFiles).toHaveBeenCalled() }) it('should update a file via handleUpdateFile', () => { mockStoreFiles = [{ id: 'file-1', name: 'a.txt', progress: 0 }] as FileEntity[] const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleUpdateFile({ id: 'file-1', name: 'a.txt', progress: 50 } as FileEntity) expect(mockSetFiles).toHaveBeenCalled() }) it('should not update file when id is not found', () => { mockStoreFiles = [{ id: 'file-1', name: 'a.txt' }] as FileEntity[] const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleUpdateFile({ id: 'nonexistent' } as FileEntity) expect(mockSetFiles).toHaveBeenCalled() }) it('should remove a file via handleRemoveFile', () => { mockStoreFiles = [{ id: 'file-1', name: 'a.txt' }] as FileEntity[] const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleRemoveFile('file-1') expect(mockSetFiles).toHaveBeenCalled() }) it('should clear all files via handleClearFiles', () => { mockStoreFiles = [{ id: 'a' }] as FileEntity[] const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleClearFiles() expect(mockSetFiles).toHaveBeenCalledWith([]) }) describe('handleReUploadFile', () => { it('should re-upload a file and call fileUpload', () => { const originalFile = new File(['content'], 'test.txt', { type: 'text/plain' }) mockStoreFiles = [{ id: 'file-1', name: 'test.txt', type: 'text/plain', size: 100, progress: -1, transferMethod: 'local_file', supportFileType: 'document', originalFile, }] as FileEntity[] const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleReUploadFile('file-1') expect(mockSetFiles).toHaveBeenCalled() expect(mockFileUpload).toHaveBeenCalled() }) it('should not re-upload when file id is not found', () => { mockStoreFiles = [] const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleReUploadFile('nonexistent') expect(mockFileUpload).not.toHaveBeenCalled() }) it('should handle progress callback during re-upload', () => { const originalFile = new File(['content'], 'test.txt', { type: 'text/plain' }) mockStoreFiles = [{ id: 'file-1', name: 'test.txt', type: 'text/plain', size: 100, progress: -1, transferMethod: 'local_file', supportFileType: 'document', originalFile, }] as FileEntity[] const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleReUploadFile('file-1') const uploadCall = mockFileUpload.mock.calls[0][0] uploadCall.onProgressCallback(50) expect(mockSetFiles).toHaveBeenCalled() }) it('should handle success callback during re-upload', () => { const originalFile = new File(['content'], 'test.txt', { type: 'text/plain' }) mockStoreFiles = [{ id: 'file-1', name: 'test.txt', type: 'text/plain', size: 100, progress: -1, transferMethod: 'local_file', supportFileType: 'document', originalFile, }] as FileEntity[] const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleReUploadFile('file-1') const uploadCall = mockFileUpload.mock.calls[0][0] uploadCall.onSuccessCallback({ id: 'uploaded-1' }) expect(mockSetFiles).toHaveBeenCalled() }) it('should handle error callback during re-upload', () => { const originalFile = new File(['content'], 'test.txt', { type: 'text/plain' }) mockStoreFiles = [{ id: 'file-1', name: 'test.txt', type: 'text/plain', size: 100, progress: -1, transferMethod: 'local_file', supportFileType: 'document', originalFile, }] as FileEntity[] const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleReUploadFile('file-1') const uploadCall = mockFileUpload.mock.calls[0][0] uploadCall.onErrorCallback(new Error('fail')) expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })) }) }) describe('handleLoadFileFromLink', () => { it('should run startProgressTimer to increment file progress', () => { vi.useFakeTimers() mockUploadRemoteFileInfo.mockReturnValue(new Promise(() => {})) // never resolves // Set up a file in the store that has progress 0 mockStoreFiles = [{ id: 'mock-uuid', name: 'https://example.com/file.txt', type: '', size: 0, progress: 0, transferMethod: 'remote_url', supportFileType: '', }] as FileEntity[] const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLoadFileFromLink('https://example.com/file.txt') // Advance timer to trigger the interval vi.advanceTimersByTime(200) expect(mockSetFiles).toHaveBeenCalled() vi.useRealTimers() }) it('should add file and call uploadRemoteFileInfo', () => { mockUploadRemoteFileInfo.mockResolvedValue({ id: 'remote-1', mime_type: 'text/plain', size: 100, name: 'remote.txt', url: 'https://example.com/remote.txt', }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLoadFileFromLink('https://example.com/file.txt') expect(mockSetFiles).toHaveBeenCalled() expect(mockUploadRemoteFileInfo).toHaveBeenCalledWith('https://example.com/file.txt', false) }) it('should remove file when extension is not allowed', async () => { mockIsAllowedFileExtension.mockReturnValue(false) mockUploadRemoteFileInfo.mockResolvedValue({ id: 'remote-1', mime_type: 'text/plain', size: 100, name: 'remote.txt', url: 'https://example.com/remote.txt', }) const { result } = renderHook(() => useFile(defaultFileConfig)) await act(async () => { result.current.handleLoadFileFromLink('https://example.com/file.txt') await vi.waitFor(() => expect(mockUploadRemoteFileInfo).toHaveBeenCalled()) }) expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })) }) it('should use empty arrays when allowed_file_types and allowed_file_extensions are undefined', async () => { mockIsAllowedFileExtension.mockReturnValue(false) mockUploadRemoteFileInfo.mockResolvedValue({ id: 'remote-1', mime_type: 'text/plain', size: 100, name: 'remote.txt', url: 'https://example.com/remote.txt', }) const configWithUndefined = { ...defaultFileConfig, allowed_file_types: undefined, allowed_file_extensions: undefined, } as unknown as FileUpload const { result } = renderHook(() => useFile(configWithUndefined)) await act(async () => { result.current.handleLoadFileFromLink('https://example.com/file.txt') await vi.waitFor(() => expect(mockUploadRemoteFileInfo).toHaveBeenCalled()) }) expect(mockIsAllowedFileExtension).toHaveBeenCalledWith('remote.txt', 'text/plain', [], []) }) it('should remove file when remote upload fails', async () => { mockUploadRemoteFileInfo.mockRejectedValue(new Error('network error')) const { result } = renderHook(() => useFile(defaultFileConfig)) await act(async () => { result.current.handleLoadFileFromLink('https://example.com/file.txt') await vi.waitFor(() => expect(mockNotify).toHaveBeenCalled()) }) expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })) }) it('should remove file when size limit is exceeded on remote upload', async () => { mockGetSupportFileType.mockReturnValue('image') mockUploadRemoteFileInfo.mockResolvedValue({ id: 'remote-1', mime_type: 'image/png', size: 20 * 1024 * 1024, name: 'large.png', url: 'https://example.com/large.png', }) const { result } = renderHook(() => useFile(defaultFileConfig)) await act(async () => { result.current.handleLoadFileFromLink('https://example.com/large.png') await vi.waitFor(() => expect(mockUploadRemoteFileInfo).toHaveBeenCalled()) }) // File should be removed because image exceeds 10MB limit expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })) }) it('should update file on successful remote upload within limits', async () => { mockUploadRemoteFileInfo.mockResolvedValue({ id: 'remote-1', mime_type: 'text/plain', size: 100, name: 'remote.txt', url: 'https://example.com/remote.txt', }) const { result } = renderHook(() => useFile(defaultFileConfig)) await act(async () => { result.current.handleLoadFileFromLink('https://example.com/remote.txt') await vi.waitFor(() => expect(mockUploadRemoteFileInfo).toHaveBeenCalled()) }) // setFiles should be called: once for add, once for update expect(mockSetFiles).toHaveBeenCalled() }) it('should stop progress timer when file reaches 80 percent', () => { vi.useFakeTimers() mockUploadRemoteFileInfo.mockReturnValue(new Promise(() => {})) // Set up a file already at 80% progress mockStoreFiles = [{ id: 'mock-uuid', name: 'https://example.com/file.txt', type: '', size: 0, progress: 80, transferMethod: 'remote_url', supportFileType: '', }] as FileEntity[] const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLoadFileFromLink('https://example.com/file.txt') // At progress 80, the timer should stop (clearTimeout path) vi.advanceTimersByTime(200) vi.useRealTimers() }) it('should stop progress timer when progress is negative', () => { vi.useFakeTimers() mockUploadRemoteFileInfo.mockReturnValue(new Promise(() => {})) // Set up a file with negative progress (error state) mockStoreFiles = [{ id: 'mock-uuid', name: 'https://example.com/file.txt', type: '', size: 0, progress: -1, transferMethod: 'remote_url', supportFileType: '', }] as FileEntity[] const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLoadFileFromLink('https://example.com/file.txt') vi.advanceTimersByTime(200) vi.useRealTimers() }) }) describe('handleLocalFileUpload', () => { let capturedListeners: Record void)[]> let mockReaderResult: string | null beforeEach(() => { capturedListeners = {} mockReaderResult = 'data:text/plain;base64,Y29udGVudA==' class MockFileReader { result: string | null = null addEventListener(event: string, handler: () => void) { if (!capturedListeners[event]) capturedListeners[event] = [] capturedListeners[event].push(handler) } readAsDataURL() { this.result = mockReaderResult capturedListeners.load?.forEach(handler => handler()) } } vi.stubGlobal('FileReader', MockFileReader) }) afterEach(() => { vi.unstubAllGlobals() }) it('should upload a local file', () => { const file = new File(['content'], 'test.txt', { type: 'text/plain' }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(file) expect(mockSetFiles).toHaveBeenCalled() }) it('should reject file with unsupported extension', () => { mockIsAllowedFileExtension.mockReturnValue(false) const file = new File(['content'], 'test.xyz', { type: 'application/xyz' }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(file) expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })) expect(mockSetFiles).not.toHaveBeenCalled() }) it('should use empty arrays when allowed_file_types and allowed_file_extensions are undefined', () => { mockIsAllowedFileExtension.mockReturnValue(false) const file = new File(['content'], 'test.xyz', { type: 'application/xyz' }) const configWithUndefined = { ...defaultFileConfig, allowed_file_types: undefined, allowed_file_extensions: undefined, } as unknown as FileUpload const { result } = renderHook(() => useFile(configWithUndefined)) result.current.handleLocalFileUpload(file) expect(mockIsAllowedFileExtension).toHaveBeenCalledWith('test.xyz', 'application/xyz', [], []) }) it('should reject file when upload is disabled and noNeedToCheckEnable is false', () => { const disabledConfig = { ...defaultFileConfig, enabled: false } as FileUpload const file = new File(['content'], 'test.txt', { type: 'text/plain' }) const { result } = renderHook(() => useFile(disabledConfig, false)) result.current.handleLocalFileUpload(file) expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })) }) it('should reject image file exceeding size limit', () => { mockGetSupportFileType.mockReturnValue('image') const largeFile = new File([new ArrayBuffer(20 * 1024 * 1024)], 'large.png', { type: 'image/png' }) Object.defineProperty(largeFile, 'size', { value: 20 * 1024 * 1024 }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(largeFile) expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })) }) it('should reject audio file exceeding size limit', () => { mockGetSupportFileType.mockReturnValue('audio') const largeFile = new File([], 'large.mp3', { type: 'audio/mpeg' }) Object.defineProperty(largeFile, 'size', { value: 60 * 1024 * 1024 }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(largeFile) expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })) }) it('should reject video file exceeding size limit', () => { mockGetSupportFileType.mockReturnValue('video') const largeFile = new File([], 'large.mp4', { type: 'video/mp4' }) Object.defineProperty(largeFile, 'size', { value: 200 * 1024 * 1024 }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(largeFile) expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })) }) it('should reject document file exceeding size limit', () => { mockGetSupportFileType.mockReturnValue('document') const largeFile = new File([], 'large.pdf', { type: 'application/pdf' }) Object.defineProperty(largeFile, 'size', { value: 20 * 1024 * 1024 }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(largeFile) expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })) }) it('should reject custom file exceeding document size limit', () => { mockGetSupportFileType.mockReturnValue('custom') const largeFile = new File([], 'large.xyz', { type: 'application/octet-stream' }) Object.defineProperty(largeFile, 'size', { value: 20 * 1024 * 1024 }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(largeFile) expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })) }) it('should allow custom file within document size limit', () => { mockGetSupportFileType.mockReturnValue('custom') const file = new File(['content'], 'file.xyz', { type: 'application/octet-stream' }) Object.defineProperty(file, 'size', { value: 1024 }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(file) expect(mockNotify).not.toHaveBeenCalled() expect(mockSetFiles).toHaveBeenCalled() }) it('should allow document file within size limit', () => { mockGetSupportFileType.mockReturnValue('document') const file = new File(['content'], 'small.pdf', { type: 'application/pdf' }) Object.defineProperty(file, 'size', { value: 1024 }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(file) expect(mockNotify).not.toHaveBeenCalled() expect(mockSetFiles).toHaveBeenCalled() }) it('should allow file with unknown type (default case)', () => { mockGetSupportFileType.mockReturnValue('unknown') const file = new File(['content'], 'test.bin', { type: 'application/octet-stream' }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(file) // Should not be rejected - unknown type passes checkSizeLimit expect(mockNotify).not.toHaveBeenCalled() }) it('should allow image file within size limit', () => { mockGetSupportFileType.mockReturnValue('image') const file = new File(['content'], 'small.png', { type: 'image/png' }) Object.defineProperty(file, 'size', { value: 1024 }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(file) expect(mockNotify).not.toHaveBeenCalled() expect(mockSetFiles).toHaveBeenCalled() }) it('should allow audio file within size limit', () => { mockGetSupportFileType.mockReturnValue('audio') const file = new File(['content'], 'small.mp3', { type: 'audio/mpeg' }) Object.defineProperty(file, 'size', { value: 1024 }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(file) expect(mockNotify).not.toHaveBeenCalled() expect(mockSetFiles).toHaveBeenCalled() }) it('should allow video file within size limit', () => { mockGetSupportFileType.mockReturnValue('video') const file = new File(['content'], 'small.mp4', { type: 'video/mp4' }) Object.defineProperty(file, 'size', { value: 1024 }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(file) expect(mockNotify).not.toHaveBeenCalled() expect(mockSetFiles).toHaveBeenCalled() }) it('should set base64Url for image files during upload', () => { mockGetSupportFileType.mockReturnValue('image') const file = new File(['content'], 'photo.png', { type: 'image/png' }) Object.defineProperty(file, 'size', { value: 1024 }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(file) expect(mockSetFiles).toHaveBeenCalled() // The file should have been added with base64Url set (for image type) const addedFiles = mockSetFiles.mock.calls[0][0] expect(addedFiles[0].base64Url).toBe('data:text/plain;base64,Y29udGVudA==') }) it('should set empty base64Url for non-image files during upload', () => { mockGetSupportFileType.mockReturnValue('document') const file = new File(['content'], 'doc.pdf', { type: 'application/pdf' }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(file) expect(mockSetFiles).toHaveBeenCalled() const addedFiles = mockSetFiles.mock.calls[0][0] expect(addedFiles[0].base64Url).toBe('') }) it('should call fileUpload with callbacks after FileReader loads', () => { const file = new File(['content'], 'test.txt', { type: 'text/plain' }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(file) expect(mockFileUpload).toHaveBeenCalled() const uploadCall = mockFileUpload.mock.calls[0][0] // Test progress callback uploadCall.onProgressCallback(50) expect(mockSetFiles).toHaveBeenCalled() // Test success callback uploadCall.onSuccessCallback({ id: 'uploaded-1' }) expect(mockSetFiles).toHaveBeenCalled() }) it('should handle fileUpload error callback', () => { const file = new File(['content'], 'test.txt', { type: 'text/plain' }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(file) const uploadCall = mockFileUpload.mock.calls[0][0] uploadCall.onErrorCallback(new Error('upload failed')) expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })) }) it('should handle FileReader error event', () => { capturedListeners = {} const errorListeners: (() => void)[] = [] class ErrorFileReader { result: string | null = null addEventListener(event: string, handler: () => void) { if (event === 'error') errorListeners.push(handler) if (!capturedListeners[event]) capturedListeners[event] = [] capturedListeners[event].push(handler) } readAsDataURL() { // Simulate error instead of load errorListeners.forEach(handler => handler()) } } vi.stubGlobal('FileReader', ErrorFileReader) const file = new File(['content'], 'test.txt', { type: 'text/plain' }) const { result } = renderHook(() => useFile(defaultFileConfig)) result.current.handleLocalFileUpload(file) expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })) }) }) describe('handleClipboardPasteFile', () => { it('should handle file paste from clipboard', () => { const file = new File(['content'], 'pasted.png', { type: 'image/png' }) const { result } = renderHook(() => useFile(defaultFileConfig)) const event = { clipboardData: { files: [file], getData: () => '', }, preventDefault: vi.fn(), } as unknown as React.ClipboardEvent result.current.handleClipboardPasteFile(event) expect(event.preventDefault).toHaveBeenCalled() }) it('should not handle paste when text is present', () => { const file = new File(['content'], 'pasted.png', { type: 'image/png' }) const { result } = renderHook(() => useFile(defaultFileConfig)) const event = { clipboardData: { files: [file], getData: () => 'some text', }, preventDefault: vi.fn(), } as unknown as React.ClipboardEvent result.current.handleClipboardPasteFile(event) expect(event.preventDefault).not.toHaveBeenCalled() }) }) describe('drag and drop handlers', () => { it('should set isDragActive on drag enter', () => { const { result } = renderHook(() => useFile(defaultFileConfig)) const event = { preventDefault: vi.fn(), stopPropagation: vi.fn() } as unknown as React.DragEvent act(() => { result.current.handleDragFileEnter(event) }) expect(result.current.isDragActive).toBe(true) }) it('should call preventDefault on drag over', () => { const { result } = renderHook(() => useFile(defaultFileConfig)) const event = { preventDefault: vi.fn(), stopPropagation: vi.fn() } as unknown as React.DragEvent result.current.handleDragFileOver(event) expect(event.preventDefault).toHaveBeenCalled() }) it('should unset isDragActive on drag leave', () => { const { result } = renderHook(() => useFile(defaultFileConfig)) const enterEvent = { preventDefault: vi.fn(), stopPropagation: vi.fn() } as unknown as React.DragEvent act(() => { result.current.handleDragFileEnter(enterEvent) }) expect(result.current.isDragActive).toBe(true) const leaveEvent = { preventDefault: vi.fn(), stopPropagation: vi.fn() } as unknown as React.DragEvent act(() => { result.current.handleDragFileLeave(leaveEvent) }) expect(result.current.isDragActive).toBe(false) }) it('should handle file drop', () => { const file = new File(['content'], 'dropped.txt', { type: 'text/plain' }) const { result } = renderHook(() => useFile(defaultFileConfig)) const event = { preventDefault: vi.fn(), stopPropagation: vi.fn(), dataTransfer: { files: [file] }, } as unknown as React.DragEvent act(() => { result.current.handleDropFile(event) }) expect(event.preventDefault).toHaveBeenCalled() expect(result.current.isDragActive).toBe(false) }) it('should not upload when no file is dropped', () => { const { result } = renderHook(() => useFile(defaultFileConfig)) const event = { preventDefault: vi.fn(), stopPropagation: vi.fn(), dataTransfer: { files: [] }, } as unknown as React.DragEvent act(() => { result.current.handleDropFile(event) }) // No file upload should be triggered expect(mockSetFiles).not.toHaveBeenCalled() }) }) describe('noop handlers', () => { it('should have handleLoadFileFromLinkSuccess as noop', () => { const { result } = renderHook(() => useFile(defaultFileConfig)) expect(() => result.current.handleLoadFileFromLinkSuccess()).not.toThrow() }) it('should have handleLoadFileFromLinkError as noop', () => { const { result } = renderHook(() => useFile(defaultFileConfig)) expect(() => result.current.handleLoadFileFromLinkError()).not.toThrow() }) }) })