import { render, screen, waitFor, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { vi, test, expect, beforeEach } from 'vitest'; import UploadButton from '../../src/components/UploadButton'; import { uploadDocument } from '../../src/api/client'; vi.mock('../../src/api/client'); beforeEach(() => { vi.mocked(uploadDocument).mockReset(); }); test('opens the upload modal when the button is clicked', async () => { const user = userEvent.setup(); render(); expect(screen.queryByTestId('upload-modal')).not.toBeInTheDocument(); await user.click(screen.getByRole('button', { name: '上传文档' })); expect(screen.getByTestId('upload-modal')).toBeInTheDocument(); }); test('selecting a file through the hidden input uploads it', async () => { vi.mocked(uploadDocument).mockResolvedValue({ key: '产品文档/新文档.md', name: '新文档.md', size: 42 }); const onUploaded = vi.fn(); const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: '上传文档' })); const file = new File(['# 新文档'], '新文档.md', { type: 'text/markdown' }); const input = screen.getByTestId('upload-file-input'); await user.upload(input, file); expect(uploadDocument).toHaveBeenCalledWith(file, '产品文档'); await waitFor(() => { expect(onUploaded).toHaveBeenCalledWith({ key: '产品文档/新文档.md', name: '新文档.md', size: 42 }); }); }); test('a failed upload shows an error message and keeps the modal open for retry', async () => { vi.mocked(uploadDocument).mockRejectedValue(new Error('Failed to upload document: 400')); const onUploaded = vi.fn(); const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: '上传文档' })); const file = new File(['data'], '恶意程序.exe', { type: 'application/octet-stream' }); const input = screen.getByTestId('upload-file-input'); await user.upload(input, file); await waitFor(() => { expect(screen.getByTestId('upload-error')).toHaveTextContent('Failed to upload document: 400'); }); expect(screen.getByTestId('upload-modal')).toBeInTheDocument(); expect(onUploaded).not.toHaveBeenCalled(); }); test('selecting a folder through the folder input uploads each file with its relative path', async () => { vi.mocked(uploadDocument).mockResolvedValue({ key: '产品文档/子目录/新文档.md', name: '新文档.md', size: 8 }); const onUploaded = vi.fn(); const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: '上传文档' })); const file = new File(['content'], '新文档.md', { type: 'text/markdown' }); Object.defineProperty(file, 'webkitRelativePath', { value: '子目录/新文档.md', configurable: true }); const input = screen.getByTestId('upload-folder-input'); await user.upload(input, file); expect(uploadDocument).toHaveBeenCalledWith(file, '产品文档', '子目录/新文档.md'); await waitFor(() => { expect(onUploaded).toHaveBeenCalled(); }); }); test('dropping a file onto the dropzone uploads it', async () => { vi.mocked(uploadDocument).mockResolvedValue({ key: '产品文档/拖拽文档.md', name: '拖拽文档.md', size: 10 }); const onUploaded = vi.fn(); const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: '上传文档' })); const file = new File(['内容'], '拖拽文档.md', { type: 'text/markdown' }); const dropzone = screen.getByTestId('dropzone'); fireEvent.drop(dropzone, { dataTransfer: { files: [file] }, }); expect(uploadDocument).toHaveBeenCalledWith(file, '产品文档'); await waitFor(() => { expect(onUploaded).toHaveBeenCalledWith({ key: '产品文档/拖拽文档.md', name: '拖拽文档.md', size: 10 }); }); }); test('dropping a folder onto the dropzone walks its entries and uploads each file with its relative path', async () => { vi.mocked(uploadDocument).mockResolvedValue({ key: '产品文档/子目录/文件.md', name: '文件.md', size: 5 }); const onUploaded = vi.fn(); const user = userEvent.setup(); const file = new File(['# hi'], '文件.md', { type: 'text/markdown' }); const fileEntry = { isFile: true, isDirectory: false, fullPath: '/子目录/文件.md', file: (success: (f: File) => void) => success(file), }; let readCount = 0; const dirReader = { readEntries: (success: (entries: unknown[]) => void) => { readCount += 1; success(readCount === 1 ? [fileEntry] : []); }, }; const dirEntry = { isFile: false, isDirectory: true, fullPath: '/子目录', createReader: () => dirReader, }; const item = { webkitGetAsEntry: () => dirEntry }; render(); await user.click(screen.getByRole('button', { name: '上传文档' })); const dropzone = screen.getByTestId('dropzone'); fireEvent.drop(dropzone, { dataTransfer: { items: [item], files: [] }, }); await waitFor(() => { expect(uploadDocument).toHaveBeenCalledWith(file, '产品文档', '子目录/文件.md'); }); await waitFor(() => { expect(onUploaded).toHaveBeenCalled(); }); });