mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 17:38:04 +08:00
Co-authored-by: CodingOnStar <hanxujiang@dify.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
211 lines
8.3 KiB
TypeScript
211 lines
8.3 KiB
TypeScript
import type { RefObject } from 'react'
|
|
import type { UploadDropzoneProps } from './upload-dropzone'
|
|
import { fireEvent, render, screen } from '@testing-library/react'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import UploadDropzone from './upload-dropzone'
|
|
|
|
// Helper to create mock ref objects for testing
|
|
const createMockRef = <T,>(value: T | null = null): RefObject<T | null> => ({ current: value })
|
|
|
|
// Mock react-i18next
|
|
vi.mock('react-i18next', () => ({
|
|
useTranslation: () => ({
|
|
t: (key: string, options?: Record<string, unknown>) => {
|
|
const translations: Record<string, string> = {
|
|
'stepOne.uploader.button': 'Drag and drop files, or',
|
|
'stepOne.uploader.buttonSingleFile': 'Drag and drop file, or',
|
|
'stepOne.uploader.browse': 'Browse',
|
|
'stepOne.uploader.tip': 'Supports {{supportTypes}}, Max {{size}}MB each, up to {{batchCount}} files at a time, {{totalCount}} files total',
|
|
}
|
|
let result = translations[key] || key
|
|
if (options && typeof options === 'object') {
|
|
Object.entries(options).forEach(([k, v]) => {
|
|
result = result.replace(`{{${k}}}`, String(v))
|
|
})
|
|
}
|
|
return result
|
|
},
|
|
}),
|
|
}))
|
|
|
|
describe('UploadDropzone', () => {
|
|
const defaultProps: UploadDropzoneProps = {
|
|
dropRef: createMockRef<HTMLDivElement>() as RefObject<HTMLDivElement | null>,
|
|
dragRef: createMockRef<HTMLDivElement>() as RefObject<HTMLDivElement | null>,
|
|
fileUploaderRef: createMockRef<HTMLInputElement>() as RefObject<HTMLInputElement | null>,
|
|
dragging: false,
|
|
supportBatchUpload: true,
|
|
supportTypesShowNames: 'PDF, DOCX, TXT',
|
|
fileUploadConfig: {
|
|
file_size_limit: 15,
|
|
batch_count_limit: 5,
|
|
file_upload_limit: 10,
|
|
},
|
|
acceptTypes: ['.pdf', '.docx', '.txt'],
|
|
onSelectFile: vi.fn(),
|
|
onFileChange: vi.fn(),
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe('rendering', () => {
|
|
it('should render the dropzone container', () => {
|
|
const { container } = render(<UploadDropzone {...defaultProps} />)
|
|
const dropzone = container.querySelector('[class*="border-dashed"]')
|
|
expect(dropzone).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render hidden file input', () => {
|
|
render(<UploadDropzone {...defaultProps} />)
|
|
const input = document.getElementById('fileUploader') as HTMLInputElement
|
|
expect(input).toBeInTheDocument()
|
|
expect(input).toHaveClass('hidden')
|
|
expect(input).toHaveAttribute('type', 'file')
|
|
})
|
|
|
|
it('should render upload icon', () => {
|
|
render(<UploadDropzone {...defaultProps} />)
|
|
const icon = document.querySelector('svg')
|
|
expect(icon).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render browse label when extensions are allowed', () => {
|
|
render(<UploadDropzone {...defaultProps} />)
|
|
expect(screen.getByText('Browse')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should not render browse label when no extensions allowed', () => {
|
|
render(<UploadDropzone {...defaultProps} acceptTypes={[]} />)
|
|
expect(screen.queryByText('Browse')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should render file size and count limits', () => {
|
|
render(<UploadDropzone {...defaultProps} />)
|
|
const tipText = screen.getByText(/Supports.*Max.*15MB/i)
|
|
expect(tipText).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('file input configuration', () => {
|
|
it('should allow multiple files when supportBatchUpload is true', () => {
|
|
render(<UploadDropzone {...defaultProps} supportBatchUpload={true} />)
|
|
const input = document.getElementById('fileUploader') as HTMLInputElement
|
|
expect(input).toHaveAttribute('multiple')
|
|
})
|
|
|
|
it('should not allow multiple files when supportBatchUpload is false', () => {
|
|
render(<UploadDropzone {...defaultProps} supportBatchUpload={false} />)
|
|
const input = document.getElementById('fileUploader') as HTMLInputElement
|
|
expect(input).not.toHaveAttribute('multiple')
|
|
})
|
|
|
|
it('should set accept attribute with correct types', () => {
|
|
render(<UploadDropzone {...defaultProps} acceptTypes={['.pdf', '.docx']} />)
|
|
const input = document.getElementById('fileUploader') as HTMLInputElement
|
|
expect(input).toHaveAttribute('accept', '.pdf,.docx')
|
|
})
|
|
})
|
|
|
|
describe('text content', () => {
|
|
it('should show batch upload text when supportBatchUpload is true', () => {
|
|
render(<UploadDropzone {...defaultProps} supportBatchUpload={true} />)
|
|
expect(screen.getByText(/Drag and drop files/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should show single file text when supportBatchUpload is false', () => {
|
|
render(<UploadDropzone {...defaultProps} supportBatchUpload={false} />)
|
|
expect(screen.getByText(/Drag and drop file/i)).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('dragging state', () => {
|
|
it('should apply dragging styles when dragging is true', () => {
|
|
const { container } = render(<UploadDropzone {...defaultProps} dragging={true} />)
|
|
const dropzone = container.querySelector('[class*="border-components-dropzone-border-accent"]')
|
|
expect(dropzone).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render drag overlay when dragging', () => {
|
|
const dragRef = createMockRef<HTMLDivElement>()
|
|
render(<UploadDropzone {...defaultProps} dragging={true} dragRef={dragRef as RefObject<HTMLDivElement | null>} />)
|
|
const overlay = document.querySelector('.absolute.left-0.top-0')
|
|
expect(overlay).toBeInTheDocument()
|
|
})
|
|
|
|
it('should not render drag overlay when not dragging', () => {
|
|
render(<UploadDropzone {...defaultProps} dragging={false} />)
|
|
const overlay = document.querySelector('.absolute.left-0.top-0')
|
|
expect(overlay).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('event handlers', () => {
|
|
it('should call onSelectFile when browse label is clicked', () => {
|
|
const onSelectFile = vi.fn()
|
|
render(<UploadDropzone {...defaultProps} onSelectFile={onSelectFile} />)
|
|
|
|
const browseLabel = screen.getByText('Browse')
|
|
fireEvent.click(browseLabel)
|
|
|
|
expect(onSelectFile).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should call onFileChange when files are selected', () => {
|
|
const onFileChange = vi.fn()
|
|
render(<UploadDropzone {...defaultProps} onFileChange={onFileChange} />)
|
|
|
|
const input = document.getElementById('fileUploader') as HTMLInputElement
|
|
const file = new File(['content'], 'test.pdf', { type: 'application/pdf' })
|
|
|
|
fireEvent.change(input, { target: { files: [file] } })
|
|
|
|
expect(onFileChange).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|
|
|
|
describe('refs', () => {
|
|
it('should attach dropRef to drop container', () => {
|
|
const dropRef = createMockRef<HTMLDivElement>()
|
|
render(<UploadDropzone {...defaultProps} dropRef={dropRef as RefObject<HTMLDivElement | null>} />)
|
|
expect(dropRef.current).toBeInstanceOf(HTMLDivElement)
|
|
})
|
|
|
|
it('should attach fileUploaderRef to input element', () => {
|
|
const fileUploaderRef = createMockRef<HTMLInputElement>()
|
|
render(<UploadDropzone {...defaultProps} fileUploaderRef={fileUploaderRef as RefObject<HTMLInputElement | null>} />)
|
|
expect(fileUploaderRef.current).toBeInstanceOf(HTMLInputElement)
|
|
})
|
|
|
|
it('should attach dragRef to overlay when dragging', () => {
|
|
const dragRef = createMockRef<HTMLDivElement>()
|
|
render(<UploadDropzone {...defaultProps} dragging={true} dragRef={dragRef as RefObject<HTMLDivElement | null>} />)
|
|
expect(dragRef.current).toBeInstanceOf(HTMLDivElement)
|
|
})
|
|
})
|
|
|
|
describe('styling', () => {
|
|
it('should have base dropzone styling', () => {
|
|
const { container } = render(<UploadDropzone {...defaultProps} />)
|
|
const dropzone = container.querySelector('[class*="border-dashed"]')
|
|
expect(dropzone).toBeInTheDocument()
|
|
expect(dropzone).toHaveClass('rounded-xl')
|
|
})
|
|
|
|
it('should have cursor-pointer on browse label', () => {
|
|
render(<UploadDropzone {...defaultProps} />)
|
|
const browseLabel = screen.getByText('Browse')
|
|
expect(browseLabel).toHaveClass('cursor-pointer')
|
|
})
|
|
})
|
|
|
|
describe('accessibility', () => {
|
|
it('should have an accessible file input', () => {
|
|
render(<UploadDropzone {...defaultProps} />)
|
|
const input = document.getElementById('fileUploader') as HTMLInputElement
|
|
expect(input).toHaveAttribute('id', 'fileUploader')
|
|
})
|
|
})
|
|
})
|