/** * 文件上传组件测试 */ import { describe, it, expect, vi, beforeEach } from 'vitest' import { mount } from '@vue/test-utils' import { ElUpload, ElButton } from 'element-plus' import FileUpload from '@/components/upload/FileUpload.vue' // 模拟 Element Plus 组件 vi.mock('element-plus', () => ({ ElUpload: { name: 'ElUpload', template: '
', props: ['action', 'headers', 'data', 'multiple', 'accept', 'limit', 'fileList', 'beforeUpload', 'onProgress', 'onSuccess', 'onError', 'onRemove', 'onExceed', 'autoUpload', 'showFileList', 'drag', 'disabled'] }, ElButton: { name: 'ElButton', template: '', props: ['type', 'disabled'] }, ElIcon: { name: 'ElIcon', template: '' }, ElProgress: { name: 'ElProgress', template: '
', props: ['percentage', 'status', 'strokeWidth'] } })) // 模拟图标组件 vi.mock('@element-plus/icons-vue', () => ({ UploadFilled: { name: 'UploadFilled' }, Upload: { name: 'Upload' }, Document: { name: 'Document' }, Picture: { name: 'Picture' } })) // 模拟认证状态 vi.mock('@/stores/auth', () => ({ useAuthStore: () => ({ token: 'mock-token' }) })) // 模拟配置 vi.mock('@/config/constants', () => ({ UPLOAD_CONFIG: { DEFAULT_UPLOAD_URL: '/api/upload', IMAGE_TYPES: ['image/jpeg', 'image/png', 'image/gif'], DOCUMENT_TYPES: ['application/pdf', 'application/msword'], VIDEO_TYPES: ['video/mp4', 'video/avi'], AUDIO_TYPES: ['audio/mp3', 'audio/wav'] } })) // 模拟格式化工具 vi.mock('@/utils/format', () => ({ formatFileSize: (size: number) => `${size} B` })) describe('FileUpload', () => { let wrapper: any beforeEach(() => { wrapper = mount(FileUpload, { props: { action: '/api/upload', multiple: false, accept: 'image/*', limit: 5, maxSize: 1024 * 1024, // 1MB autoUpload: true } }) }) afterEach(() => { wrapper?.unmount() }) it('should render correctly', () => { expect(wrapper.exists()).toBe(true) expect(wrapper.find('.file-upload').exists()).toBe(true) }) it('should render upload button when not drag mode', () => { expect(wrapper.find('.el-button').exists()).toBe(true) expect(wrapper.find('.upload-dragger').exists()).toBe(false) }) it('should render drag area when drag mode is enabled', async () => { await wrapper.setProps({ drag: true }) expect(wrapper.find('.upload-dragger').exists()).toBe(true) }) it('should show upload hint', () => { expect(wrapper.find('.upload-tip').exists()).toBe(true) }) it('should emit events correctly', async () => { const file = new File(['test'], 'test.txt', { type: 'text/plain' }) // 模拟文件上传成功 await wrapper.vm.handleSuccess({ url: 'http://example.com/file.txt' }, { uid: '1', name: 'test.txt' }) expect(wrapper.emitted('success')).toBeTruthy() }) it('should validate file type', () => { const validFile = new File(['test'], 'test.jpg', { type: 'image/jpeg' }) const invalidFile = new File(['test'], 'test.txt', { type: 'text/plain' }) // 设置接受的文件类型 wrapper.vm.acceptTypes = 'image/jpeg,image/png' expect(wrapper.vm.isValidFileType(validFile)).toBe(true) expect(wrapper.vm.isValidFileType(invalidFile)).toBe(false) }) it('should validate file size', async () => { const smallFile = new File(['small'], 'small.txt', { type: 'text/plain' }) Object.defineProperty(smallFile, 'size', { value: 500 }) const largeFile = new File(['large'], 'large.txt', { type: 'text/plain' }) Object.defineProperty(largeFile, 'size', { value: 2 * 1024 * 1024 }) // 2MB // 测试文件大小验证 const result1 = await wrapper.vm.handleBeforeUpload(smallFile) expect(result1).toBe(true) const result2 = await wrapper.vm.handleBeforeUpload(largeFile) expect(result2).toBe(false) }) it('should handle upload progress', () => { const progressEvent = { percent: 50 } const file = { uid: '1', name: 'test.txt' } wrapper.vm.handleProgress(progressEvent, file) expect(wrapper.vm.uploadPercent).toBe(50) expect(wrapper.emitted('progress')).toBeTruthy() }) it('should handle upload error', () => { const error = new Error('Upload failed') const file = { uid: '1', name: 'test.txt' } wrapper.vm.handleError(error, file) expect(wrapper.vm.uploadStatus).toBe('exception') expect(wrapper.emitted('error')).toBeTruthy() }) it('should handle file removal', () => { const file = { uid: '1', name: 'test.txt' } wrapper.vm.handleRemove(file) expect(wrapper.emitted('remove')).toBeTruthy() }) it('should handle exceed limit', () => { wrapper.vm.handleExceed() // 应该显示警告消息(这里我们只能检查方法是否被调用) expect(true).toBe(true) // 占位断言 }) it('should clear files', () => { wrapper.vm.fileList = [ { uid: '1', name: 'test1.txt' }, { uid: '2', name: 'test2.txt' } ] wrapper.vm.clearFiles() expect(wrapper.vm.fileList).toEqual([]) }) it('should compute upload headers correctly', () => { const headers = wrapper.vm.uploadHeaders expect(headers).toHaveProperty('Authorization') expect(headers.Authorization).toBe('Bearer mock-token') expect(headers['X-Requested-With']).toBe('XMLHttpRequest') }) it('should compute upload data correctly', async () => { await wrapper.setProps({ fileType: 'image', data: { category: 'avatar' } }) const data = wrapper.vm.uploadData expect(data.type).toBe('image') expect(data.category).toBe('avatar') }) it('should compute accept types correctly', async () => { await wrapper.setProps({ fileType: 'image' }) expect(wrapper.vm.acceptTypes).toBe('image/jpeg,image/png,image/gif') await wrapper.setProps({ fileType: 'document' }) expect(wrapper.vm.acceptTypes).toBe('application/pdf,application/msword') await wrapper.setProps({ accept: 'custom/*' }) expect(wrapper.vm.acceptTypes).toBe('custom/*') }) it('should compute upload hint correctly', async () => { await wrapper.setProps({ fileType: 'image', limit: 3, maxSize: 1024 * 1024 }) const hint = wrapper.vm.uploadHint expect(hint).toContain('最多3个文件') expect(hint).toContain('JPG、PNG、GIF') expect(hint).toContain('1024 B') // 模拟的格式化结果 }) it('should handle disabled state', async () => { await wrapper.setProps({ disabled: true }) expect(wrapper.find('.el-button').attributes('disabled')).toBeDefined() }) it('should handle custom button text and type', async () => { await wrapper.setProps({ buttonText: 'Custom Upload', buttonType: 'success' }) const button = wrapper.find('.el-button') expect(button.text()).toContain('Custom Upload') expect(button.attributes('type')).toBe('success') }) })