test: Update upload test case (#38282)

This commit is contained in:
hms181231 2022-10-31 10:16:51 +08:00 committed by GitHub
parent 46c846a34c
commit 00e5ca5ee9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 105 additions and 237 deletions

View File

@ -2,12 +2,12 @@
import produce from 'immer';
import { cloneDeep } from 'lodash';
import type { UploadRequestOption } from 'rc-upload/lib/interface';
import React from 'react';
import React, { createRef } from 'react';
import type { RcFile, UploadFile, UploadProps } from '..';
import Upload from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render, sleep, act } from '../../../tests/utils';
import { fireEvent, render, waitFakeTimer, act } from '../../../tests/utils';
import Form from '../../form';
import { resetWarned } from '../../_util/warning';
import { getFileItem, isImageUrl, removeFileItem } from '../utils';
@ -19,8 +19,17 @@ describe('Upload', () => {
mountTest(Upload);
rtlTest(Upload);
beforeAll(() => {
jest.useFakeTimers();
});
beforeEach(() => setup());
afterEach(() => teardown());
afterAll(() => {
jest.useRealTimers();
});
afterEach(() => {
jest.clearAllTimers();
return teardown();
});
// Mock for rc-util raf
window.requestAnimationFrame = callback => window.setTimeout(callback, 16);
@ -29,16 +38,18 @@ describe('Upload', () => {
// https://github.com/react-component/upload/issues/36
it('should get refs inside Upload in componentDidMount', () => {
let ref: React.ReactInstance;
let ref: React.RefObject<HTMLInputElement>;
class App extends React.Component {
inputRef = createRef<HTMLInputElement>();
componentDidMount() {
ref = this.refs.input;
ref = this.inputRef;
}
render() {
return (
<Upload supportServerRender={false}>
<input ref="input" />
<input ref={this.inputRef} />
</Upload>
);
}
@ -48,7 +59,6 @@ describe('Upload', () => {
});
it('return promise in beforeUpload', async () => {
jest.useFakeTimers();
const data = jest.fn();
const done = jest.fn();
const props: UploadProps = {
@ -74,22 +84,11 @@ describe('Upload', () => {
fireEvent.change(wrapper.querySelector('input')!, {
target: { files: [{ file: 'foo.png' }] },
});
act(() => {
jest.runAllTimers();
});
await act(async () => {
for (let i = 0; i < 4; i += 1) {
// eslint-disable-next-line no-await-in-loop
await Promise.resolve();
}
});
await waitFakeTimer();
expect(done).toHaveBeenCalled();
jest.useRealTimers();
});
it('beforeUpload can be falsy', async () => {
jest.useFakeTimers();
const done = jest.fn();
const props: UploadProps = {
action: 'http://upload.com',
@ -110,18 +109,11 @@ describe('Upload', () => {
fireEvent.change(wrapper.querySelector('input')!, {
target: { files: [{ file: 'foo.png' }] },
});
await act(async () => {
for (let i = 0; i < 4; i += 1) {
// eslint-disable-next-line no-await-in-loop
await Promise.resolve();
}
});
await waitFakeTimer();
expect(done).toHaveBeenCalled();
jest.useRealTimers();
});
it('upload promise return file in beforeUpload', async () => {
jest.useFakeTimers();
const done = jest.fn();
const data = jest.fn();
const props: UploadProps = {
@ -153,18 +145,9 @@ describe('Upload', () => {
fireEvent.change(wrapper.querySelector('input')!, {
target: { files: [{ file: 'foo.png' }] },
});
act(() => {
jest.runAllTimers();
});
await act(async () => {
for (let i = 0; i < 4; i += 1) {
// eslint-disable-next-line no-await-in-loop
await Promise.resolve();
}
});
await waitFakeTimer();
expect(done).toHaveBeenCalled();
jest.useRealTimers();
});
it('should not stop upload when return value of beforeUpload is false', done => {
@ -298,8 +281,7 @@ describe('Upload', () => {
expect(wrapper.querySelectorAll('input#upload').length).toBe(0);
});
it('should be controlled by fileList', () => {
jest.useFakeTimers();
it('should be controlled by fileList', async () => {
const fileList = [
{
uid: '-1',
@ -312,11 +294,8 @@ describe('Upload', () => {
const { rerender } = render(<Upload ref={ref} />);
expect(ref.current.fileList).toEqual([]);
rerender(<Upload ref={ref} fileList={fileList as UploadProps['fileList']} />);
act(() => {
jest.runAllTimers();
});
await waitFakeTimer();
expect(ref.current.fileList).toEqual(fileList);
jest.useRealTimers();
});
it('should be able to get uid at first', () => {
@ -451,7 +430,7 @@ describe('Upload', () => {
expect(linkNode?.getAttribute('rel')).toBe('noopener');
});
it('should not stop remove when return value of onRemove is false', done => {
it('should stop remove when return value of onRemove is false', async () => {
const mockRemove = jest.fn(() => false);
const props: UploadProps = {
onRemove: mockRemove,
@ -469,12 +448,11 @@ describe('Upload', () => {
fireEvent.click(wrapper.querySelector('div.ant-upload-list-item .anticon-delete')!);
setTimeout(() => {
expect(mockRemove).toHaveBeenCalled();
expect(props.fileList).toHaveLength(1);
expect(props.fileList?.[0]?.status).toBe('done');
done();
});
await waitFakeTimer();
expect(mockRemove).toHaveBeenCalled();
expect(props.fileList).toHaveLength(1);
expect(props.fileList?.[0]?.status).toBe('done');
});
// https://github.com/ant-design/ant-design/issues/18902
@ -504,13 +482,8 @@ describe('Upload', () => {
);
fireEvent.click(container.querySelector('div.ant-upload-list-item .anticon-delete')!);
// uploadStart is a batch work which we need wait for react act
await act(async () => {
await Promise.resolve();
});
// Delay return true for remove
await sleep(100);
await waitFakeTimer();
await act(async () => {
await removePromise(true);
});
@ -519,7 +492,7 @@ describe('Upload', () => {
expect(file.status).toBe('removed');
});
it('should not stop download when return use onDownload', done => {
it('should not stop download when return use onDownload', async () => {
const mockRemove = jest.fn(() => false);
const props: UploadProps = {
onRemove: mockRemove,
@ -540,11 +513,9 @@ describe('Upload', () => {
fireEvent.click(wrapper.querySelector('div.ant-upload-list-item .anticon-download')!);
setTimeout(() => {
expect(props.fileList).toHaveLength(1);
expect(props.fileList?.[0]?.status).toBe('done');
done();
});
await waitFakeTimer();
expect(props.fileList).toHaveLength(1);
expect(props.fileList?.[0]?.status).toBe('done');
});
// https://github.com/ant-design/ant-design/issues/14439
@ -658,7 +629,6 @@ describe('Upload', () => {
// https://github.com/ant-design/ant-design/issues/26427
it('should sync file list with control mode', async () => {
jest.useFakeTimers();
const done = jest.fn();
let callTimes = 0;
@ -718,21 +688,9 @@ describe('Upload', () => {
target: { files: [{ file: 'foo.png' }] },
});
await act(async () => {
for (let i = 0; i < 3; i += 1) {
// eslint-disable-next-line no-await-in-loop
await Promise.resolve();
}
});
act(() => {
jest.runAllTimers();
});
await act(async () => {
await Promise.resolve();
});
await waitFakeTimer();
expect(done).toHaveBeenCalled();
jest.useRealTimers();
});
describe('maxCount', () => {
@ -764,7 +722,7 @@ describe('Upload', () => {
},
});
await sleep(20);
await waitFakeTimer();
expect(onChange.mock.calls[0][0].fileList).toHaveLength(1);
expect(onChange.mock.calls[0][0].fileList[0]).toEqual(
@ -807,7 +765,7 @@ describe('Upload', () => {
},
});
await sleep(20);
await waitFakeTimer();
expect(onChange.mock.calls[0][0].fileList).toHaveLength(2);
expect(onChange.mock.calls[0][0].fileList).toEqual([
@ -850,7 +808,7 @@ describe('Upload', () => {
},
});
await sleep();
await waitFakeTimer();
const { file } = onChange.mock.calls[0][0];
const clone = cloneDeep(file);
@ -875,6 +833,8 @@ describe('Upload', () => {
},
];
const image = cloneDeep(fileList[0]);
const frozenFileList = fileList.map(Object.freeze);
const { container: wrapper } = render(
@ -884,11 +844,10 @@ describe('Upload', () => {
fireEvent.click(rmBtn[rmBtn.length - 1]);
// Wait for Upload async remove
await act(async () => {
await sleep();
});
});
await waitFakeTimer();
expect(image).toEqual(frozenFileList[0]);
});
// https://github.com/ant-design/ant-design/issues/30390
// IE11 Does not support the File constructor
it('should not break in IE if beforeUpload returns false', async () => {
@ -906,7 +865,7 @@ describe('Upload', () => {
});
// React 18 is async now
await sleep();
await waitFakeTimer();
expect(onChange.mock.calls[0][0].fileList).toHaveLength(1);
spyIE.mockRestore();
@ -914,8 +873,6 @@ describe('Upload', () => {
// https://github.com/ant-design/ant-design/issues/33819
it('should show the animation of the upload children leaving when the upload children becomes null', async () => {
jest.useFakeTimers();
const { container, rerender } = render(
<Upload listType="picture-card">
<button type="button">upload</button>
@ -931,16 +888,12 @@ describe('Upload', () => {
});
// Motion leave status change: start > active
act(() => {
jest.runAllTimers();
});
await waitFakeTimer();
fireEvent.animationEnd(container.querySelector('.ant-upload-select-picture-card')!);
expect(container.querySelector('.ant-upload-select-picture-card')).not.toHaveClass(
'ant-upload-animate-inline-leave-start',
);
jest.useRealTimers();
});
it('<Upload /> should pass <UploadList /> prefixCls', async () => {
@ -978,9 +931,8 @@ describe('Upload', () => {
});
// React 18 is async now
await act(async () => {
await sleep();
});
await waitFakeTimer();
onChange.mockReset();
// Processing

View File

@ -1,6 +1,6 @@
import React from 'react';
import Upload from '..';
import { fireEvent, render, sleep, waitFor, act } from '../../../tests/utils';
import { act, fireEvent, render, waitFakeTimer, waitFor } from '../../../tests/utils';
import Form from '../../form';
import UploadList from '../UploadList';
import { previewImage } from '../utils';
@ -54,10 +54,15 @@ describe('Upload List', () => {
// HTMLCanvasElement.prototype
beforeEach(() => setup());
beforeEach(() => {
jest.useFakeTimers();
return setup();
});
afterEach(() => {
teardown();
drawImageCallback = null;
jest.clearAllTimers();
jest.useRealTimers();
});
let open: jest.MockInstance<any, any[]>;
@ -109,8 +114,6 @@ describe('Upload List', () => {
// 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',
@ -138,36 +141,22 @@ describe('Upload List', () => {
);
// 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();
});
await waitFakeTimer();
// 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();
});
await waitFakeTimer();
expect(container.querySelectorAll('.ant-upload-list-text-container')).toHaveLength(1);
unmount();
jest.useRealTimers();
});
it('should be uploading when upload a file', async () => {
jest.useFakeTimers();
const done = jest.fn();
let wrapper: ReturnType<typeof render>;
let latestFileList: UploadFile<any>[] | null = null;
@ -195,23 +184,14 @@ describe('Upload List', () => {
fireEvent.change(wrapper.container.querySelector('input')!, {
target: { files: [{ name: 'foo.png' }] },
});
await act(async () => {
await Promise.resolve();
await Promise.resolve();
});
act(() => {
jest.runAllTimers();
});
await waitFakeTimer();
expect(done).toHaveBeenCalled();
wrapper.unmount();
jest.useRealTimers();
});
it('handle error', async () => {
jest.useFakeTimers();
const onChange = jest.fn();
const {
@ -231,14 +211,9 @@ describe('Upload List', () => {
target: { files: [{ name: 'foo.png' }] },
});
await act(async () => {
await Promise.resolve();
});
await waitFakeTimer();
// Wait twice since `errorRequest` also use timeout for mock
act(() => {
jest.runAllTimers();
});
expect(onChange).toHaveBeenLastCalledWith(
expect.objectContaining({
@ -249,21 +224,15 @@ describe('Upload List', () => {
fireEvent.animationEnd(wrapper.querySelector('.ant-upload-animate-appear-active')!);
}
act(() => {
jest.runAllTimers();
});
await waitFakeTimer();
expect(wrapper.firstChild).toMatchSnapshot();
// Error message
fireEvent.mouseEnter(wrapper.querySelector('.ant-upload-list-item')!);
act(() => {
jest.runAllTimers();
});
await waitFakeTimer();
expect(baseElement.querySelector('.ant-tooltip')).not.toHaveClass('.ant-tooltip-hidden');
jest.useRealTimers();
unmount();
});
@ -285,7 +254,7 @@ describe('Upload List', () => {
target: { files: [{ name: 'foo.png' }] },
});
await sleep();
await waitFakeTimer();
expect(ref.current.fileList.length).toBe(fileList.length + 1);
expect(handleChange.mock.calls[0][0].fileList).toHaveLength(3);
@ -381,8 +350,8 @@ describe('Upload List', () => {
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);
await waitFakeTimer();
expect(handleChange).toHaveBeenCalledTimes(2);
unmount();
});
@ -409,6 +378,7 @@ describe('Upload List', () => {
</Upload>,
);
fireEvent.click(wrapper.querySelectorAll('.anticon-download')[0]);
expect(handleDownload).toHaveBeenCalled();
unmount();
});
@ -467,7 +437,7 @@ describe('Upload List', () => {
<button type="button">upload</button>
</Upload>,
);
await sleep();
await waitFakeTimer();
expect(ref.current.fileList[2].thumbUrl).not.toBe(undefined);
expect(onDrawImage).toHaveBeenCalled();
@ -559,8 +529,6 @@ describe('Upload List', () => {
});
it('not crash when uploading not provides percent', async () => {
jest.useFakeTimers();
const { unmount } = render(
<Upload
listType="picture"
@ -570,13 +538,9 @@ describe('Upload List', () => {
/>,
);
act(() => {
jest.runAllTimers();
});
await waitFakeTimer();
unmount();
jest.useRealTimers();
});
it('should support showRemoveIcon and showPreviewIcon', () => {
@ -640,8 +604,8 @@ describe('Upload List', () => {
fireEvent.click(wrapper.querySelectorAll('.custom-delete')[1]);
expect(handleRemove).toHaveBeenCalledWith(fileList[1]);
expect(myClick).toHaveBeenCalled();
await sleep();
expect(handleChange.mock.calls.length).toBe(2);
await waitFakeTimer();
expect(handleChange).toHaveBeenCalledTimes(2);
unmount();
});
@ -718,11 +682,9 @@ describe('Upload List', () => {
rules={[
{
required: true,
validator(_, value, callback) {
async validator(_, value) {
if (!value || value.length === 0) {
callback('file required');
} else {
callback();
throw new Error('file required');
}
},
},
@ -739,7 +701,7 @@ describe('Upload List', () => {
const { container, unmount } = render(<TestForm />);
fireEvent.submit(container.querySelector('form')!);
await sleep();
await waitFakeTimer();
expect(formRef!.getFieldError(['file'])).toEqual(['file required']);
fireEvent.change(container.querySelector('input')!, {
@ -747,7 +709,7 @@ describe('Upload List', () => {
});
fireEvent.submit(container.querySelector('form')!);
await sleep();
await waitFakeTimer();
expect(formRef!.getFieldError(['file'])).toEqual([]);
unmount();
@ -795,7 +757,8 @@ describe('Upload List', () => {
target: { files: [{ name: 'foo.png' }] },
});
await sleep();
await waitFakeTimer();
expect(wrapper.querySelector('.ant-upload-list-item-thumbnail')?.getAttribute('href')).toBe(
null,
);
@ -1005,7 +968,7 @@ describe('Upload List', () => {
</Upload>,
);
expect(previewFile).toHaveBeenCalledWith(file.originFileObj);
await sleep(100);
await waitFakeTimer();
expect(
wrapper.querySelector('.ant-upload-list-item-thumbnail img')?.getAttribute('src'),
@ -1039,7 +1002,6 @@ describe('Upload List', () => {
);
const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img');
expect(imgNode.length).toBe(2);
unmount();
});
it('should render <img /> when custom imageUrl return true', () => {
@ -1056,7 +1018,6 @@ describe('Upload List', () => {
const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img');
expect(isImageUrl).toHaveBeenCalled();
expect(imgNode.length).toBe(3);
unmount();
});
it('should not render <img /> when custom imageUrl return false', () => {
@ -1073,31 +1034,11 @@ describe('Upload List', () => {
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 <img /> when upload non-image file and configure thumbUrl in onChange', async () => {
const thumbUrl =
'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png';
@ -1140,20 +1081,12 @@ describe('Upload List', () => {
});
// Wait for `rc-upload` process file
await waitPromise();
// Wait for mock request finish request
act(() => {
jest.runAllTimers();
});
await waitFakeTimer();
// Basic called times
expect(onChange).toHaveBeenCalled();
// Check for images
act(() => {
jest.runAllTimers();
});
await waitFakeTimer();
const afterImgNode = wrapper.container.querySelectorAll(
'.ant-upload-list-item-thumbnail img',
);
@ -1162,29 +1095,25 @@ describe('Upload List', () => {
wrapper.unmount();
});
it('should not render <img /> when upload non-image file without thumbUrl in onChange', done => {
it('should not render <img /> when upload non-image file without thumbUrl in onChange', async () => {
(global as any).testName =
'should not render <img /> when upload non-image file without thumbUrl in onChange';
let wrapper: ReturnType<typeof render>;
const onChange: UploadProps['onChange'] = async ({ fileList: files }) => {
wrapper.rerender(
<Upload
action="http://jsonplaceholder.typicode.com/posts/"
listType="picture-card"
fileList={files}
onChange={onChange}
customRequest={successRequest}
>
<button type="button">upload</button>
</Upload>,
);
await sleep();
const imgNode = wrapper.container.querySelectorAll('.ant-upload-list-item-thumbnail img');
expect(imgNode.length).toBe(0);
done();
};
const onChange = jest.fn<void, Record<'fileList', UploadProps['fileList']>[]>(
({ fileList: files }) => {
wrapper.rerender(
<Upload
action="http://jsonplaceholder.typicode.com/posts/"
listType="picture-card"
fileList={files}
onChange={onChange}
customRequest={successRequest}
>
<button type="button">upload</button>
</Upload>,
);
},
);
wrapper = render(
<Upload
action="http://jsonplaceholder.typicode.com/posts/"
@ -1201,10 +1130,17 @@ describe('Upload List', () => {
fireEvent.change(wrapper.container.querySelector('input')!, {
target: { files: [nonImageFile] },
});
await waitFakeTimer();
expect(onChange).toHaveBeenCalled();
expect(wrapper.container.querySelectorAll('.ant-upload-list-item-thumbnail img').length).toBe(
0,
);
});
});
it('[deprecated] should support transformFile', done => {
jest.useRealTimers();
let wrapper: ReturnType<typeof render>;
let lastFile: UploadFile;
@ -1281,8 +1217,6 @@ describe('Upload List', () => {
// 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<any>();
const MyUpload: React.FC = () => {
@ -1320,14 +1254,10 @@ describe('Upload List', () => {
expect(uploadRef.current.fileList).toHaveLength(fileNames.length);
act(() => {
jest.runAllTimers();
});
await waitFakeTimer();
expect(uploadRef.current.fileList).toHaveLength(fileNames.length);
unmount();
jest.useRealTimers();
});
it('itemRender', () => {
@ -1384,13 +1314,11 @@ describe('Upload List', () => {
const beforeUpload = jest.fn(() => Upload.LIST_IGNORE);
const { container: wrapper, unmount } = render(<Upload beforeUpload={beforeUpload} />);
await act(() => {
fireEvent.change(wrapper.querySelector('input')!, {
target: { files: [{ file: 'foo.png' }] },
});
fireEvent.change(wrapper.querySelector('input')!, {
target: { files: [{ file: 'foo.png' }] },
});
await sleep();
await waitFakeTimer();
expect(beforeUpload).toHaveBeenCalled();
expect(wrapper.querySelectorAll('.ant-upload-list-text-container')).toHaveLength(0);
@ -1585,8 +1513,6 @@ describe('Upload List', () => {
// https://github.com/ant-design/ant-design/issues/36286
it('remove should keep origin className', async () => {
jest.useFakeTimers();
const onChange = jest.fn();
const list = [
{
@ -1605,15 +1531,7 @@ describe('Upload List', () => {
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();
});
await waitFakeTimer();
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
@ -1622,7 +1540,5 @@ describe('Upload List', () => {
);
expect(container.querySelector('.ant-upload-list-item-error')).toBeTruthy();
jest.useRealTimers();
});
});