import React from 'react'; import Upload from '..'; import { fireEvent, render, sleep, waitFor, act } from '../../../tests/utils'; import Form from '../../form'; import UploadList from '../UploadList'; import { previewImage } from '../utils'; import { setup, teardown } from './mock'; import { errorRequest, successRequest } from './requests'; import type { FormInstance } from '../../form'; import type { UploadFile, UploadProps } from '..'; import type { UploadListProps, UploadLocale } from '../interface'; const fileList: UploadProps['fileList'] = [ { uid: '-1', name: 'xxx.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png', }, { uid: '-2', name: 'yyy.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png', thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', }, ]; describe('Upload List', () => { // Mock for rc-util raf window.requestAnimationFrame = callback => window.setTimeout(callback, 16); window.cancelAnimationFrame = id => window.clearTimeout(id); // jsdom not support `createObjectURL` yet. Let's handle this. const originCreateObjectURL = window.URL.createObjectURL; window.URL.createObjectURL = jest.fn(() => ''); // Mock dom let size = { width: 0, height: 0 }; function setSize(width: number, height: number) { size = { width, height }; } const mockWidthGet = jest.spyOn(Image.prototype, 'width', 'get'); const mockHeightGet = jest.spyOn(Image.prototype, 'height', 'get'); const mockSrcSet = jest.spyOn(Image.prototype, 'src', 'set'); let drawImageCallback: jest.Mock | null = null; function hookDrawImageCall(callback: jest.Mock) { drawImageCallback = callback; } const mockGetCanvasContext = jest.spyOn(HTMLCanvasElement.prototype, 'getContext'); const mockToDataURL = jest.spyOn(HTMLCanvasElement.prototype, 'toDataURL'); // HTMLCanvasElement.prototype beforeEach(() => setup()); afterEach(() => { teardown(); drawImageCallback = null; }); let open: jest.MockInstance; beforeAll(() => { open = jest.spyOn(window, 'open').mockImplementation(() => null); mockWidthGet.mockImplementation(() => size.width); mockHeightGet.mockImplementation(() => size.height); mockSrcSet.mockImplementation(function fn() { if (this.onload) { this.onload(); } }); mockGetCanvasContext.mockReturnValue({ drawImage(...args) { if (drawImageCallback) { drawImageCallback(...args); } }, } as RenderingContext); mockToDataURL.mockReturnValue('data:image/png;base64,'); }); afterAll(() => { window.URL.createObjectURL = originCreateObjectURL; mockWidthGet.mockRestore(); mockHeightGet.mockRestore(); mockSrcSet.mockRestore(); mockGetCanvasContext.mockRestore(); mockToDataURL.mockRestore(); open.mockRestore(); }); // https://github.com/ant-design/ant-design/issues/4653 it('should use file.thumbUrl for in priority', () => { const { container: wrapper, unmount } = render( , ); fileList.forEach((file, i) => { const linkNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail')[i]; const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img')[i]; expect(linkNode.getAttribute('href')).toBe(file.url); expect(imgNode.getAttribute('src')).toBe(file.thumbUrl); }); unmount(); }); // https://github.com/ant-design/ant-design/issues/7269 it('should remove correct item when uid is 0', async () => { jest.useFakeTimers(); const list = [ { uid: '0', name: 'xxx.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png', }, { uid: '1', name: 'xxx.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png', }, ]; const { container, unmount } = render( , ); expect(container.querySelectorAll('.ant-upload-list-item').length).toBe(2); fireEvent.click( container.querySelectorAll('.ant-upload-list-item')[0].querySelector('.anticon-delete')!, ); // Upload use Promise to wait remove action. Let's wait this also. await act(async () => { for (let i = 0; i < 10; i += 1) { // eslint-disable-next-line no-await-in-loop await Promise.resolve(); } }); // Progress motion to active act(() => { jest.runAllTimers(); }); // Progress motion to done // React 17 will reach deadline, so we need check if already done if (container.querySelector('.ant-upload-animate-leave-active')) { fireEvent.animationEnd(container.querySelector('.ant-upload-animate-leave-active')!); } act(() => { jest.runAllTimers(); }); expect(container.querySelectorAll('.ant-upload-list-item-container')).toHaveLength(1); unmount(); jest.useRealTimers(); }); it('should be uploading when upload a file', async () => { jest.useFakeTimers(); const done = jest.fn(); let wrapper: ReturnType; let latestFileList: UploadFile[] | null = null; const onChange: UploadProps['onChange'] = async ({ file, fileList: eventFileList }) => { expect(eventFileList === latestFileList).toBeFalsy(); if (file.status === 'uploading') { await Promise.resolve(); expect(wrapper.container.firstChild).toMatchSnapshot(); } if (file.status === 'done') { done(); } latestFileList = eventFileList; }; wrapper = render( , ); fireEvent.change(wrapper.container.querySelector('input')!, { target: { files: [{ name: 'foo.png' }] }, }); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); act(() => { jest.runAllTimers(); }); expect(done).toHaveBeenCalled(); wrapper.unmount(); jest.useRealTimers(); }); it('handle error', async () => { jest.useFakeTimers(); const onChange = jest.fn(); const { container: wrapper, unmount, baseElement, } = render( , ); fireEvent.change(wrapper.querySelector('input')!, { target: { files: [{ name: 'foo.png' }] }, }); await act(async () => { await Promise.resolve(); }); // Wait twice since `errorRequest` also use timeout for mock act(() => { jest.runAllTimers(); }); expect(onChange).toHaveBeenLastCalledWith( expect.objectContaining({ file: expect.objectContaining({ status: 'error' }), }), ); if (wrapper.querySelector('.ant-upload-animate-appear-active')) { fireEvent.animationEnd(wrapper.querySelector('.ant-upload-animate-appear-active')!); } act(() => { jest.runAllTimers(); }); expect(wrapper.firstChild).toMatchSnapshot(); // Error message fireEvent.mouseEnter(wrapper.querySelector('.ant-upload-list-item')!); act(() => { jest.runAllTimers(); }); expect(baseElement.querySelector('.ant-tooltip')).not.toHaveClass('.ant-tooltip-hidden'); jest.useRealTimers(); unmount(); }); it('does concat fileList when beforeUpload returns false', async () => { const handleChange = jest.fn(); const ref = React.createRef(); const { container: wrapper, unmount } = render( false} > , ); fireEvent.change(wrapper.querySelector('input')!, { target: { files: [{ name: 'foo.png' }] }, }); await sleep(); expect(ref.current.fileList.length).toBe(fileList.length + 1); expect(handleChange.mock.calls[0][0].fileList).toHaveLength(3); unmount(); }); it('In the case of listType=picture, the error status does not show the download.', () => { (global as any).testName = 'In the case of listType=picture, the error status does not show the download.'; const file = { status: 'error', uid: 'file' }; const { container: wrapper, unmount } = render( , ); // Has error item className fireEvent.mouseEnter(wrapper.querySelector('.ant-upload-list-item-error')!); expect(wrapper.querySelectorAll('div.ant-upload-list-item i.anticon-download').length).toBe(0); unmount(); }); it('In the case of listType=picture-card, the error status does not show the download.', () => { (global as any).testName = 'In the case of listType=picture-card, the error status does not show the download.'; const file = { status: 'error', uid: 'file' }; const { container: wrapper, unmount } = render( , ); expect(wrapper.querySelectorAll('div.ant-upload-list-item i.anticon-download').length).toBe(0); unmount(); }); it('In the case of listType=text, the error status does not show the download.', () => { const file = { status: 'error', uid: 'file' }; const { container: wrapper, unmount } = render( , ); expect(wrapper.querySelectorAll('div.ant-upload-list-item i.anticon-download').length).toBe(0); unmount(); }); it('should support onPreview', () => { const handlePreview = jest.fn(); const { container: wrapper, unmount } = render( , ); fireEvent.click(wrapper.querySelectorAll('.anticon-eye')[0]); expect(handlePreview).toHaveBeenCalledWith(fileList[0]); fireEvent.click(wrapper.querySelectorAll('.anticon-eye')[1]); expect(handlePreview).toHaveBeenCalledWith(fileList[1]); unmount(); }); it('should support onRemove', async () => { const handleRemove = jest.fn(); const handleChange = jest.fn(); const { container: wrapper, unmount } = render( , ); fireEvent.click(wrapper.querySelectorAll('.anticon-delete')[0]); expect(handleRemove).toHaveBeenCalledWith(fileList[0]); fireEvent.click(wrapper.querySelectorAll('.anticon-delete')[1]); expect(handleRemove).toHaveBeenCalledWith(fileList[1]); await sleep(); expect(handleChange.mock.calls.length).toBe(2); unmount(); }); it('should support onDownload', async () => { const handleDownload = jest.fn(); const { container: wrapper, unmount } = render( , ); fireEvent.click(wrapper.querySelectorAll('.anticon-download')[0]); unmount(); }); it('should support no onDownload', async () => { const { container: wrapper, unmount } = render( , ); fireEvent.click(wrapper.querySelectorAll('.anticon-download')[0]); unmount(); }); describe('should generate thumbUrl from file', () => { [ { width: 100, height: 200, name: 'height large than width' }, { width: 200, height: 100, name: 'width large than height' }, ].forEach(({ width, height, name }) => { it(name, async () => { setSize(width, height); const onDrawImage = jest.fn(); hookDrawImageCall(onDrawImage); const handlePreview = jest.fn(); const newFileList: UploadProps['fileList'] = [...fileList]; const newFile = { ...fileList[0], uid: '-3', originFileObj: new File([], 'xxx.png', { type: 'image/png' }), }; delete newFile.thumbUrl; newFileList.push(newFile as UploadFile); const ref = React.createRef(); const { unmount } = render( , ); await sleep(); expect(ref.current.fileList[2].thumbUrl).not.toBe(undefined); expect(onDrawImage).toHaveBeenCalled(); // Offset check const [, offsetX, offsetY] = onDrawImage.mock.calls[0]; expect((width > height ? offsetX : offsetY) === 0).toBeTruthy(); unmount(); }); }); }); it('should non-image format file preview', () => { const list = [ { name: 'not-image', status: 'done', uid: '-3', url: 'https://cdn.xxx.com/aaa.zip', thumbUrl: 'data:application/zip;base64,UEsDBAoAAAAAADYZYkwAAAAAAAAAAAAAAAAdAAk', originFileObj: new File([], 'aaa.zip'), }, { name: 'image', status: 'done', uid: '-4', url: 'https://cdn.xxx.com/aaa', }, { name: 'not-image', status: 'done', uid: '-5', url: 'https://cdn.xxx.com/aaa.xx', }, { name: 'not-image', status: 'done', uid: '-6', url: 'https://cdn.xxx.com/aaa.png/xx.xx', }, { name: 'image', status: 'done', uid: '-7', url: 'https://cdn.xxx.com/xx.xx/aaa.png', }, { name: 'image', status: 'done', uid: '-8', url: 'https://cdn.xxx.com/xx.xx/aaa.png', thumbUrl: 'data:image/png;base64,UEsDBAoAAAAAADYZYkwAAAAAAAAAAAAAAAAdAAk', }, { name: 'image', status: 'done', uid: '-9', url: 'https://cdn.xxx.com/xx.xx/aaa.png?query=123', }, { name: 'image', status: 'done', uid: '-10', url: 'https://cdn.xxx.com/xx.xx/aaa.png#anchor', }, { name: 'image', status: 'done', uid: '-11', url: 'https://cdn.xxx.com/xx.xx/aaa.png?query=some.query.with.dot', }, { name: 'image', status: 'done', uid: '-12', url: 'https://publish-pic-cpu.baidu.com/1296beb3-50d9-4276-885f-52645cbb378e.jpeg@w_228%2ch_152', type: 'image/png', }, ]; const { container: wrapper, unmount } = render( , ); expect(wrapper.firstChild).toMatchSnapshot(); unmount(); }); it('not crash when uploading not provides percent', async () => { jest.useFakeTimers(); const { unmount } = render( , ); act(() => { jest.runAllTimers(); }); unmount(); jest.useRealTimers(); }); it('should support showRemoveIcon and showPreviewIcon', () => { const list = [ { name: 'image', status: 'uploading', uid: '-4', url: 'https://cdn.xxx.com/aaa', }, { name: 'image', status: 'done', uid: '-5', url: 'https://cdn.xxx.com/aaa', }, ]; const { container: wrapper, unmount } = render( , ); expect(wrapper.firstChild).toMatchSnapshot(); unmount(); }); it('should support custom onClick in custom icon', async () => { const handleRemove = jest.fn(); const handleChange = jest.fn(); const myClick = jest.fn(); const { container: wrapper, unmount } = render( RM ), }} > , ); fireEvent.click(wrapper.querySelectorAll('.custom-delete')[0]); expect(handleRemove).toHaveBeenCalledWith(fileList[0]); expect(myClick).toHaveBeenCalled(); fireEvent.click(wrapper.querySelectorAll('.custom-delete')[1]); expect(handleRemove).toHaveBeenCalledWith(fileList[1]); expect(myClick).toHaveBeenCalled(); await sleep(); expect(handleChange.mock.calls.length).toBe(2); unmount(); }); it('should support removeIcon and downloadIcon', () => { const list = [ { name: 'image', status: 'uploading', uid: '-4', url: 'https://cdn.xxx.com/aaa', }, { name: 'image', status: 'done', uid: '-5', url: 'https://cdn.xxx.com/aaa', }, ]; const { container: wrapper, unmount } = render( RM, downloadIcon: DL, previewIcon: PV, }} > , ); expect(wrapper.firstChild).toMatchSnapshot(); unmount(); const { container: wrapper2, unmount: unmount2 } = render( RM, downloadIcon: () => DL, previewIcon: () => PV, }} > , ); expect(wrapper2.firstChild).toMatchSnapshot(); unmount2(); }); // https://github.com/ant-design/ant-design/issues/7762 it('work with form validation', async () => { let formRef: FormInstance; const TestForm: React.FC = () => { const [form] = Form.useForm(); formRef = form; return (
e.fileList} rules={[ { required: true, validator(_, value, callback) { if (!value || value.length === 0) { callback('file required'); } else { callback(); } }, }, ]} > false}>
); }; const { container, unmount } = render(); fireEvent.submit(container.querySelector('form')!); await sleep(); expect(formRef!.getFieldError(['file'])).toEqual(['file required']); fireEvent.change(container.querySelector('input')!, { target: { files: [{ name: 'foo.png' }] }, }); fireEvent.submit(container.querySelector('form')!); await sleep(); expect(formRef!.getFieldError(['file'])).toEqual([]); unmount(); }); it('return when prop onPreview not exists', () => { const ref = React.createRef(); const { unmount } = render( , ); expect(ref.current?.handlePreview?.()).toBe(undefined); unmount(); }); it('return when prop onDownload not exists', () => { const file = new File([''], 'test.txt', { type: 'text/plain' }); const items = [{ uid: 'upload-list-item', url: '' }]; const ref = React.createRef(); const showUploadList = { showUploadList: { showDownloadIcon: true } }; const { unmount } = render( , ); expect(ref.current?.handleDownload?.(file)).toBe(undefined); unmount(); }); it('previewFile should work correctly', async () => { const items = [{ uid: 'upload-list-item', url: '' }]; const previewFunc = jest.fn(previewImage); const { container: wrapper, unmount } = render( , ); fireEvent.change(wrapper.querySelector('input')!, { target: { files: [{ name: 'foo.png' }] }, }); await sleep(); expect(wrapper.querySelector('.ant-upload-list-item-thumbnail')?.getAttribute('href')).toBe( null, ); unmount(); }); it('downloadFile should work correctly', async () => { const downloadFunc = jest.fn(); const items = [{ uid: 'upload-list-item', name: 'test', url: '', status: 'done' }]; const { container: wrapper, unmount } = render( , ); // Not throw const btn = wrapper.querySelector('.ant-btn'); expect(btn?.getAttribute('title')).toBe('Download file'); fireEvent.click(btn!); expect(downloadFunc).toHaveBeenCalled(); unmount(); }); it('extname should work correctly when url not exists', () => { const items = [{ uid: 'upload-list-item', url: '' }]; const { container: wrapper, unmount } = render( , ); expect(wrapper.querySelectorAll('.ant-upload-list-item-thumbnail').length).toBe(1); unmount(); }); it('extname should work correctly when url exists', done => { const items = [{ status: 'done', uid: 'upload-list-item', url: '/example' }]; const { container: wrapper, unmount } = render( { expect(file.url).toBe('/example'); unmount(); done(); }} items={items as UploadListProps['items']} locale={{ downloadFile: '' }} showDownloadIcon />, ); fireEvent.click(wrapper.querySelector('div.ant-upload-list-item .anticon-download')!); }); it('when picture-card is loading, icon should render correctly', () => { const items = [{ status: 'uploading', uid: 'upload-list-item' }]; const { container: wrapper, unmount } = render( , ); expect(wrapper.querySelectorAll('.ant-upload-list-item-thumbnail')?.length).toBe(1); expect(wrapper.querySelector('.ant-upload-list-item-thumbnail')?.textContent).toBe('uploading'); unmount(); }); it('onPreview should be called, when url exists', () => { const onPreview = jest.fn(); const items = [{ thumbUrl: 'thumbUrl', url: 'url', uid: 'upload-list-item' }]; const { container: wrapper, rerender, unmount, } = render( , ); fireEvent.click(wrapper.querySelector('.ant-upload-list-item-thumbnail')!); expect(onPreview).toHaveBeenCalled(); fireEvent.click(wrapper.querySelector('.ant-upload-list-item-name')!); expect(onPreview).toHaveBeenCalled(); rerender( , ); fireEvent.click(wrapper.querySelector('.ant-upload-list-item-name')!); expect(onPreview).toHaveBeenCalled(); unmount(); }); it('upload image file should be converted to the base64', async () => { const mockFile = new File([''], 'foo.png', { type: 'image/png', }); const previewFunc = jest.fn(previewImage); const { unmount } = render( , ); await waitFor(() => { expect(previewFunc).toHaveBeenCalled(); }); await previewFunc(mockFile).then(dataUrl => { expect(dataUrl).toEqual('data:image/png;base64,'); }); unmount(); }); it('upload svg file with should not have CORS error', async () => { const mockFile = new File( [ '
Test
', ], 'bar.svg', { type: 'image/svg+xml' }, ); const previewFunc = jest.fn(previewImage); const { unmount } = render( , ); await waitFor(() => { expect(previewFunc).toHaveBeenCalled(); }); await previewFunc(mockFile).then(dataUrl => { expect(dataUrl).toEqual('data:image/png;base64,'); }); unmount(); }); it("upload non image file shouldn't be converted to the base64", async () => { const mockFile = new File([''], 'foo.7z', { type: 'application/x-7z-compressed', }); const previewFunc = jest.fn(previewImage); const { unmount } = render( , ); await waitFor(() => { expect(previewFunc).toHaveBeenCalled(); }); await previewFunc(mockFile).then(dataUrl => { expect(dataUrl).toBe(''); }); unmount(); }); describe('customize previewFile support', () => { function test(name: string, renderInstance: () => File | Blob) { it(name, async () => { const mockThumbnail = 'mock-image'; const previewFile = jest.fn(() => Promise.resolve(mockThumbnail)); const file = { ...fileList?.[0], originFileObj: renderInstance(), }; delete file.thumbUrl; const ref = React.createRef(); const { container: wrapper, unmount } = render( , ); expect(previewFile).toHaveBeenCalledWith(file.originFileObj); await sleep(100); expect( wrapper.querySelector('.ant-upload-list-item-thumbnail img')?.getAttribute('src'), ).toBe(mockThumbnail); unmount(); }); } test('File', () => new File([], 'xxx.png')); test('Blob', () => new Blob()); }); // https://github.com/ant-design/ant-design/issues/22958 describe('customize isImageUrl support', () => { const list = [ ...fileList, { uid: '0', name: 'xxx.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', thumbUrl: 'http://image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg@!panda_style?spm=a2c4g.11186623.2.17.4dc56b29BHokyg&file=example.jpg@!panda_style', }, ]; it('should not render when file.thumbUrl use "!" as separator', () => { const { container: wrapper, unmount } = render( , ); const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img'); expect(imgNode.length).toBe(2); unmount(); }); it('should render when custom imageUrl return true', () => { const isImageUrl = jest.fn(() => true); const { container: wrapper, unmount } = render( , ); const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img'); expect(isImageUrl).toHaveBeenCalled(); expect(imgNode.length).toBe(3); unmount(); }); it('should not render when custom imageUrl return false', () => { const isImageUrl = jest.fn(() => false); const { container: wrapper, unmount } = render( , ); const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img'); expect(isImageUrl).toHaveBeenCalled(); expect(imgNode.length).toBe(0); unmount(); }); }); describe('thumbUrl support for non-image', () => { beforeEach(() => { jest.useFakeTimers(); }); afterEach(() => { jest.useRealTimers(); }); const nonImageFile = new File([''], 'foo.7z', { type: 'application/x-7z-compressed' }); /** Wait for a long promise since `rc-util` internal has at least 3 promise wait */ async function waitPromise() { /* eslint-disable no-await-in-loop */ for (let i = 0; i < 10; i += 1) { await Promise.resolve(); } /* eslint-enable */ } it('should render when upload non-image file and configure thumbUrl in onChange', async () => { const thumbUrl = 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'; let wrapper: ReturnType; const onChange = jest.fn[]>( ({ fileList: files }) => { const newFileList = files?.map>(item => ({ ...item, thumbUrl })); wrapper.rerender( , ); }, ); wrapper = render( , ); const imgNode = wrapper.container.querySelectorAll('.ant-upload-list-item-thumbnail img'); expect(imgNode.length).toBe(0); // Simulate change is a timeout change fireEvent.change(wrapper.container.querySelector('input')!, { target: { files: [nonImageFile] }, }); // Wait for `rc-upload` process file await waitPromise(); // Wait for mock request finish request act(() => { jest.runAllTimers(); }); // Basic called times expect(onChange).toHaveBeenCalled(); // Check for images act(() => { jest.runAllTimers(); }); const afterImgNode = wrapper.container.querySelectorAll( '.ant-upload-list-item-thumbnail img', ); expect(afterImgNode.length).toBeTruthy(); wrapper.unmount(); }); it('should not render when upload non-image file without thumbUrl in onChange', done => { (global as any).testName = 'should not render when upload non-image file without thumbUrl in onChange'; let wrapper: ReturnType; const onChange: UploadProps['onChange'] = async ({ fileList: files }) => { wrapper.rerender( , ); await sleep(); const imgNode = wrapper.container.querySelectorAll('.ant-upload-list-item-thumbnail img'); expect(imgNode.length).toBe(0); done(); }; wrapper = render( , ); const imgNode = wrapper.container.querySelectorAll('.ant-upload-list-item-thumbnail img'); expect(imgNode.length).toBe(0); fireEvent.change(wrapper.container.querySelector('input')!, { target: { files: [nonImageFile] }, }); }); }); it('[deprecated] should support transformFile', done => { let wrapper: ReturnType; let lastFile: UploadFile; const handleTransformFile = jest.fn(); const onChange: UploadProps['onChange'] = ({ file }) => { if (file.status === 'done') { expect(file).not.toBe(lastFile); expect(handleTransformFile).toHaveBeenCalled(); wrapper.unmount(); done(); } lastFile = file; }; wrapper = render( , ); fireEvent.change(wrapper.container.querySelector('input')!, { target: { files: [{ name: 'foo.png' }] }, }); }); it('should render button inside UploadList when listStyle is picture-card', () => { const { container: wrapper, rerender, unmount, } = render( , ); expect(wrapper.querySelectorAll('.ant-upload-list button.trigger').length).toBeGreaterThan(0); rerender( , ); expect(wrapper.querySelectorAll('.ant-upload-list button.trigger').length).toBe(0); unmount(); }); // https://github.com/ant-design/ant-design/issues/26536 it('multiple file upload should keep the internal fileList async', async () => { jest.useFakeTimers(); const uploadRef = React.createRef(); const MyUpload: React.FC = () => { const [testFileList, setTestFileList] = React.useState([]); return ( { setTestFileList([...info.fileList]); }} > ); }; const { unmount } = render(); // Mock async update in a frame const fileNames = ['light', 'bamboo', 'little']; await act(() => { uploadRef.current.onBatchStart( fileNames.map(fileName => { const file = new File([], fileName); (file as any).uid = fileName; return { file, parsedFile: file }; }), ); }); expect(uploadRef.current.fileList).toHaveLength(fileNames.length); act(() => { jest.runAllTimers(); }); expect(uploadRef.current.fileList).toHaveLength(fileNames.length); unmount(); jest.useRealTimers(); }); it('itemRender', () => { const onDownload = jest.fn(); const onRemove = jest.fn(); const onPreview = jest.fn(); const itemRender: UploadListProps['itemRender'] = (_, file, currFileList, actions) => { const { name, status, uid, url } = file; const index = currFileList.indexOf(file); return (
{`uid:${uid} name: ${name} status: ${status} url: ${url} ${index + 1}/${ currFileList.length }`} remove download preview
); }; const { container: wrapper, unmount } = render( , ); expect(wrapper.firstChild).toMatchSnapshot(); fireEvent.click(wrapper.querySelectorAll('.custom-item-render-action-remove')[0]); expect(onRemove.mock.calls[0][0]).toEqual(fileList[0]); fireEvent.click(wrapper.querySelectorAll('.custom-item-render-action-download')[0]); expect(onDownload.mock.calls[0][0]).toEqual(fileList[0]); fireEvent.click(wrapper.querySelectorAll('.custom-item-render-action-preview')[0]); expect(onPreview.mock.calls[0][0]).toEqual(fileList[0]); unmount(); }); it('LIST_IGNORE should not add in list', async () => { const beforeUpload = jest.fn(() => Upload.LIST_IGNORE); const { container: wrapper, unmount } = render(); await act(() => { fireEvent.change(wrapper.querySelector('input')!, { target: { files: [{ file: 'foo.png' }] }, }); }); await sleep(); expect(beforeUpload).toHaveBeenCalled(); expect(wrapper.querySelectorAll('.ant-upload-list-text-container')).toHaveLength(0); unmount(); }); it('Not crash when fileList is null', () => { const defaultWrapper = render( , ); defaultWrapper.unmount(); const wrapper = render(); wrapper.unmount(); }); it('should not exist crossorigin attribute when does not set file.crossorigin in case of listType="picture"', () => { const list = [ { uid: '0', name: 'xxx.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png', }, ]; const { container: wrapper, unmount } = render( , ); list.forEach((_, i) => { const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img')[i]; expect(imgNode.getAttribute('crossOrigin')).toBe(null); }); unmount(); }); it('should exist crossorigin attribute when set file.crossorigin in case of listType="picture"', () => { const list = [ { uid: '0', name: 'xxx.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png', crossOrigin: '', }, { uid: '1', name: 'xxx.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png', crossOrigin: 'anonymous', }, { uid: '2', name: 'xxx.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png', crossOrigin: 'use-credentials', }, ]; const { container: wrapper, unmount } = render( , ); list.forEach((file, i) => { const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img')[i]; expect(imgNode.getAttribute('crossOrigin')).not.toBe(undefined); expect(imgNode.getAttribute('crossOrigin')).toBe(file.crossOrigin); }); unmount(); }); it('should not exist crossorigin attribute when does not set file.crossorigin in case of listType="picture-card"', () => { const list = [ { uid: '0', name: 'xxx.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png', }, ]; const { container: wrapper, unmount } = render( , ); list.forEach((_, i) => { const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img')[i]; expect(imgNode.getAttribute('crossOrigin')).toBe(null); }); unmount(); }); it('should exist crossorigin attribute when set file.crossorigin in case of listType="picture-card"', () => { const list = [ { uid: '0', name: 'xxx.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png', crossOrigin: '', }, { uid: '1', name: 'xxx.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png', crossOrigin: 'anonymous', }, { uid: '2', name: 'xxx.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png', crossOrigin: 'use-credentials', }, ]; const { container: wrapper, unmount } = render( , ); list.forEach((file, i) => { const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img')[i]; expect(imgNode.getAttribute('crossOrigin')).not.toBe(undefined); expect(imgNode.getAttribute('crossOrigin')).toBe(file.crossOrigin); }); unmount(); }); describe('should not display upload file-select button when listType is picture-card and children is empty', () => { it('when showUploadList is true', () => { const list = [ { uid: '0', name: 'xxx.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png', }, ]; const { container: wrapper, unmount } = render( , ); expect(wrapper.querySelectorAll('.ant-upload-select').length).toBe(1); expect(wrapper.querySelectorAll('.ant-upload-select')[0]?.style.display).toBe( 'none', ); unmount(); }); // https://github.com/ant-design/ant-design/issues/36183 it('when showUploadList is false', () => { const list = [ { uid: '0', name: 'xxx.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png', }, ]; const { container: wrapper, unmount } = render( , ); expect(wrapper.querySelectorAll('.ant-upload-select').length).toBe(1); expect(wrapper.querySelectorAll('.ant-upload-select')[0]?.style.display).toBe( 'none', ); unmount(); }); }); // https://github.com/ant-design/ant-design/issues/36286 it('remove should keep origin className', async () => { jest.useFakeTimers(); const onChange = jest.fn(); const list = [ { uid: '0', name: 'xxx.png', status: 'error', }, ]; const { container } = render( , ); fireEvent.click(container.querySelector('.anticon-delete')!); // Wait for Upload sync for (let i = 0; i < 10; i += 1) { // eslint-disable-next-line no-await-in-loop await Promise.resolve(); } act(() => { jest.runAllTimers(); }); expect(onChange).toHaveBeenCalledWith( expect.objectContaining({ file: expect.objectContaining({ status: 'removed' }), }), ); expect(container.querySelector('.ant-upload-list-item-error')).toBeTruthy(); jest.useRealTimers(); }); });