109 lines
3.4 KiB
TypeScript
109 lines
3.4 KiB
TypeScript
import {
|
||
hasAllowedFileExtension,
|
||
hasDragLeftDropZone,
|
||
isSupportedImageFile,
|
||
prepareFileDrag,
|
||
validateImageFiles,
|
||
} from '@/components/upload/drag-drop-utils'
|
||
|
||
function createDragEvent(
|
||
currentTarget: HTMLElement,
|
||
options: {
|
||
clientX?: number
|
||
clientY?: number
|
||
relatedTarget?: EventTarget | null
|
||
dropEffect?: DataTransfer['dropEffect']
|
||
} = {},
|
||
): React.DragEvent<HTMLElement> {
|
||
const dataTransfer = {
|
||
dropEffect: options.dropEffect || 'none',
|
||
}
|
||
|
||
return {
|
||
preventDefault: jest.fn(),
|
||
stopPropagation: jest.fn(),
|
||
currentTarget,
|
||
relatedTarget: options.relatedTarget || null,
|
||
clientX: options.clientX ?? 50,
|
||
clientY: options.clientY ?? 50,
|
||
dataTransfer,
|
||
} as unknown as React.DragEvent<HTMLElement>
|
||
}
|
||
|
||
describe('drag-drop-utils', () => {
|
||
test('经过上传区内部子元素时不应判定为离开', () => {
|
||
const dropZone = document.createElement('div')
|
||
const child = document.createElement('button')
|
||
dropZone.appendChild(child)
|
||
|
||
const event = createDragEvent(dropZone, { relatedTarget: child })
|
||
|
||
expect(hasDragLeftDropZone(event)).toBe(false)
|
||
})
|
||
|
||
test('指针越过上传区边界时应判定为离开', () => {
|
||
const dropZone = document.createElement('div')
|
||
Object.defineProperty(dropZone, 'getBoundingClientRect', {
|
||
configurable: true,
|
||
value: () => ({
|
||
left: 0,
|
||
right: 100,
|
||
top: 0,
|
||
bottom: 100,
|
||
}),
|
||
})
|
||
|
||
const event = createDragEvent(dropZone, { clientX: 120, clientY: 50 })
|
||
|
||
expect(hasDragLeftDropZone(event)).toBe(true)
|
||
})
|
||
|
||
test('禁用状态应阻止默认拖放并显示禁止效果', () => {
|
||
const dropZone = document.createElement('div')
|
||
const event = createDragEvent(dropZone)
|
||
|
||
prepareFileDrag(event, true)
|
||
|
||
expect(event.preventDefault).toHaveBeenCalled()
|
||
expect(event.stopPropagation).toHaveBeenCalled()
|
||
expect(event.dataTransfer.dropEffect).toBe('none')
|
||
})
|
||
|
||
test('Windows 未提供 MIME 时应按常见图片扩展名识别', () => {
|
||
const file = new File(['image'], 'preview.PNG', { type: '' })
|
||
|
||
expect(isSupportedImageFile(file)).toBe(true)
|
||
})
|
||
|
||
test('应同时保留有效图片并返回非法及超大文件错误', () => {
|
||
const valid = new File(['image'], 'valid.png', { type: 'image/png' })
|
||
const invalid = new File(['text'], 'invalid.txt', { type: 'text/plain' })
|
||
const oversized = new File([new Uint8Array(11)], 'large.jpg', { type: 'image/jpeg' })
|
||
|
||
const result = validateImageFiles([valid, invalid, oversized], { maxBytes: 10 })
|
||
|
||
expect(result.acceptedFiles).toEqual([valid])
|
||
expect(result.errors.join(';')).toContain('invalid.txt')
|
||
expect(result.errors.join(';')).toContain('large.jpg')
|
||
})
|
||
|
||
test('图片数量限制应考虑已经选择的图片', () => {
|
||
const files = [
|
||
new File(['1'], '1.png', { type: 'image/png' }),
|
||
new File(['2'], '2.png', { type: 'image/png' }),
|
||
new File(['3'], '3.png', { type: 'image/png' }),
|
||
]
|
||
|
||
const result = validateImageFiles(files, { maxCount: 5, existingCount: 4 })
|
||
|
||
expect(result.acceptedFiles).toEqual([files[0]])
|
||
expect(result.errors).toContain('最多只能选择 5 张图片,已忽略 2 张')
|
||
})
|
||
|
||
test('压缩包扩展名校验应忽略大小写和前导点', () => {
|
||
const file = new File(['zip'], 'model.ZIP', { type: 'application/zip' })
|
||
|
||
expect(hasAllowedFileExtension(file, ['.zip', '.rar', '.7z'])).toBe(true)
|
||
})
|
||
})
|