refactor: Upload use batch start (#29474)

* chore: Update Upload version

* useMergedStatus

* fix file list parser

* fix list replacement

* bump rc-upload

* add Upload.LIST_IGNORE

* support LIST_IGNORE

* fix uploadList batch test

* test: fix batch case test case changed

* test: Finish test case

* fix: Not change uid if exist

* use proxy

* fix proxy

* fix ts

* part test case check

* more test back

* back of test all

* update test case

* more unmount

* adjust test cae

* add unmount
This commit is contained in:
二货机器人 2021-02-26 15:18:37 +08:00 committed by GitHub
parent 5d22b0ef7a
commit 79f8fece4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 274 additions and 170 deletions

View File

@ -1,5 +1,6 @@
import * as React from 'react';
import RcUpload from 'rc-upload';
import RcUpload, { UploadProps as RcUploadProps } from 'rc-upload';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import classNames from 'classnames';
import Dragger from './Dragger';
import UploadList from './UploadList';
@ -13,19 +14,19 @@ import {
UploadType,
UploadListType,
} from './interface';
import { T, fileToObject, getFileItem, removeFileItem } from './utils';
import { T, wrapFile, getFileItem, removeFileItem } from './utils';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import defaultLocale from '../locale/default';
import { ConfigContext } from '../config-provider';
import devWarning from '../_util/devWarning';
import useForceUpdate from '../_util/hooks/useForceUpdate';
import useFreshState from './useFreshState';
const LIST_IGNORE = `__LIST_IGNORE_${Date.now()}__`;
export { UploadProps };
const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (props, ref) => {
const {
fileList: fileListProp,
fileList,
defaultFileList,
onRemove,
showUploadList,
@ -48,14 +49,11 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
maxCount,
} = props;
const [dragState, setDragState] = React.useState<string>('drop');
const forceUpdate = useForceUpdate();
const [mergedFileList, setMergedFileList] = useMergedState(defaultFileList || [], {
value: fileList,
});
// Refresh always use fresh data
const [getFileList, setFileList] = useFreshState<UploadFile<any>[]>(
fileListProp || defaultFileList || [],
fileListProp,
);
const [dragState, setDragState] = React.useState<string>('drop');
const upload = React.useRef<any>();
@ -77,13 +75,19 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
React.useEffect(() => {
const timestamp = Date.now();
(fileListProp || []).forEach((file, index) => {
file.uid = file.uid ?? `__AUTO__${timestamp}_${index}__`;
(fileList || []).forEach((file, index) => {
if (!file.uid) {
file.uid = `__AUTO__${timestamp}_${index}__`;
}
});
}, [fileListProp]);
}, [fileList]);
const onInternalChange = (info: UploadChangeParam) => {
let cloneList = [...info.fileList];
const onInternalChange = (
file: UploadFile,
changedFileList: UploadFile[],
event?: { percent: number },
) => {
let cloneList = [...changedFileList];
// Cut to match count
if (maxCount === 1) {
@ -92,19 +96,47 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
cloneList = cloneList.slice(0, maxCount);
}
setFileList(cloneList);
setMergedFileList(cloneList);
onChange?.({
...info,
const changeInfo: UploadChangeParam = {
file,
fileList: cloneList,
};
if (event) {
changeInfo.event = event;
}
onChange?.(changeInfo);
};
const onBatchStart: RcUploadProps['onBatchStart'] = batchFileInfoList => {
// Skip file which marked as `LIST_IGNORE`, these file will not add to file list
const filteredFileInfoList = batchFileInfoList.filter(info => !(info.file as any)[LIST_IGNORE]);
// Nothing to do since no file need upload
if (!filteredFileInfoList.length) {
return;
}
const objectFileList = filteredFileInfoList.map(info => wrapFile(info.file));
// Concat new files with prev files
const newFileList = [...mergedFileList];
objectFileList.forEach(fileObj => {
if (newFileList.every(existFile => existFile.uid !== fileObj.uid)) {
newFileList.push(fileObj);
}
});
onInternalChange(filteredFileInfoList[0]?.file, newFileList);
};
const onStart = (file: RcFile) => {
const targetItem = fileToObject(file);
const targetItem = wrapFile(file);
targetItem.status = 'uploading';
const nextFileList = getFileList().concat();
const nextFileList = [...mergedFileList];
const fileIndex = nextFileList.findIndex(({ uid }: UploadFile) => uid === targetItem.uid);
if (fileIndex === -1) {
@ -113,10 +145,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
nextFileList[fileIndex] = targetItem;
}
onInternalChange({
file: targetItem,
fileList: nextFileList,
});
onInternalChange(targetItem, nextFileList);
};
const onSuccess = (response: any, file: UploadFile, xhr: any) => {
@ -127,7 +156,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
} catch (e) {
/* do nothing */
}
const targetItem = getFileItem(file, getFileList());
const targetItem = getFileItem(file, mergedFileList);
// removed
if (!targetItem) {
return;
@ -135,28 +164,21 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
targetItem.status = 'done';
targetItem.response = response;
targetItem.xhr = xhr;
onInternalChange({
file: { ...targetItem },
fileList: getFileList().concat(),
});
onInternalChange(wrapFile(targetItem), [...mergedFileList]);
};
const onProgress = (e: { percent: number }, file: UploadFile) => {
const targetItem = getFileItem(file, getFileList());
const targetItem = getFileItem(file, mergedFileList);
// removed
if (!targetItem) {
return;
}
targetItem.percent = e.percent;
onInternalChange({
event: e,
file: { ...targetItem },
fileList: getFileList().concat(),
});
onInternalChange(wrapFile(targetItem), [...mergedFileList], e);
};
const onError = (error: Error, response: any, file: UploadFile) => {
const targetItem = getFileItem(file, getFileList());
const targetItem = getFileItem(file, mergedFileList);
// removed
if (!targetItem) {
return;
@ -164,10 +186,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
targetItem.error = error;
targetItem.response = response;
targetItem.status = 'error';
onInternalChange({
file: { ...targetItem },
fileList: getFileList().concat(),
});
onInternalChange(wrapFile(targetItem), [...mergedFileList]);
};
const handleRemove = (file: UploadFile) => {
@ -178,12 +197,12 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
return;
}
const fileList = getFileList();
const removedFileList = removeFileItem(file, fileList);
const removedFileList = removeFileItem(file, mergedFileList);
if (removedFileList) {
currentFile = { ...file, status: 'removed' };
fileList?.forEach(item => {
currentFile = wrapFile(file);
currentFile.status = 'removed';
mergedFileList?.forEach(item => {
const matchKey = currentFile.uid !== undefined ? 'uid' : 'name';
if (item[matchKey] === currentFile[matchKey]) {
item.status = 'removed';
@ -191,10 +210,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
});
upload.current?.abort(currentFile);
onInternalChange({
file: currentFile,
fileList: removedFileList,
});
onInternalChange(currentFile, removedFileList);
}
});
};
@ -203,43 +219,47 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
setDragState(e.type);
};
const beforeUpload = (file: RcFile, fileListArgs: RcFile[]) => {
const { beforeUpload: beforeUploadProp } = props;
if (!beforeUploadProp) {
return true;
}
const result = beforeUploadProp(file, fileListArgs);
if (result === false) {
// Get unique file list
const uniqueList: UploadFile<any>[] = [];
getFileList()
.concat(fileListArgs.map(fileToObject))
.forEach(f => {
if (uniqueList.every(uf => uf.uid !== f.uid)) {
uniqueList.push(f);
}
});
const mergedBeforeUpload = async (file: RcFile, fileListArgs: RcFile[]) => {
const { beforeUpload, transformFile } = props;
onInternalChange({
file,
fileList: uniqueList,
});
return false;
let parsedFile: File | Blob | string = file;
if (beforeUpload) {
const result = await beforeUpload(file, fileListArgs);
if (result === false) {
return false;
}
// Hack for LIST_IGNORE, we add additional info to remove from the list
delete (file as any)[LIST_IGNORE];
if ((result as any) === LIST_IGNORE) {
Object.defineProperty(file, LIST_IGNORE, {
value: true,
configurable: true,
});
return false;
}
if (typeof result === 'object' && result) {
parsedFile = result as File;
}
}
if (result && (result as PromiseLike<any>).then) {
return result;
if (transformFile) {
parsedFile = await transformFile(parsedFile as any);
}
return true;
return parsedFile as RcFile;
};
// Test needs
React.useImperativeHandle(ref, () => ({
onStart,
onSuccess,
onProgress,
onError,
fileList: getFileList(),
fileList: mergedFileList,
upload: upload.current,
forceUpdate,
}));
const { getPrefixCls, direction } = React.useContext(ConfigContext);
@ -247,13 +267,14 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
const prefixCls = getPrefixCls('upload', customizePrefixCls);
const rcUploadProps = {
onBatchStart,
onStart,
onError,
onProgress,
onSuccess,
...props,
prefixCls,
beforeUpload,
beforeUpload: mergedBeforeUpload,
onChange: undefined,
};
@ -277,7 +298,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
return (
<UploadList
listType={listType}
items={getFileList(true)}
items={mergedFileList}
previewFile={previewFile}
onPreview={onPreview}
onDownload={onDownload}
@ -306,7 +327,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
prefixCls,
{
[`${prefixCls}-drag`]: true,
[`${prefixCls}-drag-uploading`]: getFileList().some(file => file.status === 'uploading'),
[`${prefixCls}-drag-uploading`]: mergedFileList.some(file => file.status === 'uploading'),
[`${prefixCls}-drag-hover`]: dragState === 'dragover',
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-rtl`]: direction === 'rtl',
@ -365,12 +386,15 @@ interface CompoundedComponent
React.PropsWithChildren<UploadProps> & React.RefAttributes<any>
> {
Dragger: typeof Dragger;
LIST_IGNORE: {};
}
const Upload = React.forwardRef<unknown, UploadProps>(InternalUpload) as CompoundedComponent;
Upload.Dragger = Dragger;
Upload.LIST_IGNORE = LIST_IGNORE;
Upload.displayName = 'Upload';
Upload.defaultProps = {

View File

@ -5,7 +5,7 @@ import { act } from 'react-dom/test-utils';
import produce from 'immer';
import Upload from '..';
import Form from '../../form';
import { T, fileToObject, getFileItem, removeFileItem } from '../utils';
import { T, wrapFile, getFileItem, removeFileItem } from '../utils';
import { setup, teardown } from './mock';
import { resetWarned } from '../../_util/devWarning';
import mountTest from '../../../tests/shared/mountTest';
@ -291,11 +291,29 @@ describe('Upload', () => {
expect(res).toBe(true);
});
it('should be able to copy file instance', () => {
const file = new File([], 'aaa.zip');
const copiedFile = fileToObject(file);
['uid', 'lastModified', 'lastModifiedDate', 'name', 'size', 'type'].forEach(key => {
expect(key in copiedFile).toBe(true);
describe('wrapFile', () => {
it('should be able to copy file instance when Proxy not support', () => {
const file = new File([], 'aaa.zip');
const OriginProxy = global.Proxy;
global.Proxy = undefined;
const copiedFile = wrapFile(file);
['uid', 'lastModified', 'lastModifiedDate', 'name', 'size', 'type'].forEach(key => {
expect(key in copiedFile).toBe(true);
});
global.Proxy = OriginProxy;
});
it('Proxy support', () => {
const file = new File([], 'aaa.zip');
const copiedFile = wrapFile(file);
console.log(Object.keys(copiedFile));
['uid', 'lastModified', 'lastModifiedDate', 'name', 'size', 'type'].forEach(key => {
expect(key in copiedFile).toBe(true);
});
});
});
@ -617,15 +635,20 @@ describe('Upload', () => {
switch (callTimes) {
case 1:
expect(file.status).toBe(undefined);
break;
case 2:
case 3:
console.log('===>', file.status);
expect(file).toEqual(expect.objectContaining({ status: 'uploading', percent: 0 }));
break;
case 3:
case 4:
expect(file).toEqual(expect.objectContaining({ status: 'uploading', percent: 100 }));
break;
case 4:
case 5:
expect(file).toEqual(expect.objectContaining({ status: 'done', percent: 100 }));
break;

View File

@ -104,6 +104,7 @@ describe('Upload List', () => {
expect(linkNode.prop('href')).toBe(file.url);
expect(imgNode.prop('src')).toBe(file.thumbUrl);
});
wrapper.unmount();
});
// https://github.com/ant-design/ant-design/issues/7269
@ -145,6 +146,8 @@ describe('Upload List', () => {
// console.log(wrapper.html());
expect(wrapper.find('.ant-upload-list-text-container').hostNodes().length).toBe(1);
wrapper.unmount();
});
it('should be uploading when upload a file', done => {
@ -157,6 +160,7 @@ describe('Upload List', () => {
}
if (file.status === 'done') {
expect(wrapper.render()).toMatchSnapshot();
wrapper.unmount();
done();
}
@ -181,8 +185,9 @@ describe('Upload List', () => {
it('handle error', done => {
let wrapper;
const onChange = ({ file }) => {
if (file.status !== 'uploading') {
if (file.status === 'error') {
expect(wrapper.render()).toMatchSnapshot();
wrapper.unmount();
done();
}
};
@ -202,7 +207,7 @@ describe('Upload List', () => {
});
});
it('does concat filelist when beforeUpload returns false', () => {
it('does concat fileList when beforeUpload returns false', async () => {
const handleChange = jest.fn();
const ref = React.createRef();
const wrapper = mount(
@ -222,11 +227,17 @@ describe('Upload List', () => {
},
});
await sleep();
expect(ref.current.fileList.length).toBe(fileList.length + 1);
expect(handleChange.mock.calls[0][0].fileList).toHaveLength(3);
wrapper.unmount();
});
it('In the case of listType=picture, the error status does not show the download.', () => {
global.testName =
'In the case of listType=picture, the error status does not show the download.';
const file = { status: 'error', uid: 'file' };
const wrapper = mount(
<Upload listType="picture" fileList={[file]} showUploadList={{ showDownloadIcon: true }}>
@ -238,9 +249,13 @@ describe('Upload List', () => {
wrapper.find('.ant-upload-list-item-error').simulate('mouseenter');
expect(wrapper.find('div.ant-upload-list-item i.anticon-download').length).toBe(0);
wrapper.unmount();
});
it('In the case of listType=picture-card, the error status does not show the download.', () => {
global.testName =
'In the case of listType=picture-card, the error status does not show the download.';
const file = { status: 'error', uid: 'file' };
const wrapper = mount(
<Upload listType="picture-card" fileList={[file]} showUploadList={{ showDownloadIcon: true }}>
@ -248,6 +263,8 @@ describe('Upload List', () => {
</Upload>,
);
expect(wrapper.find('div.ant-upload-list-item i.anticon-download').length).toBe(0);
wrapper.unmount();
});
it('In the case of listType=text, the error status does not show the download.', () => {
@ -258,6 +275,8 @@ describe('Upload List', () => {
</Upload>,
);
expect(wrapper.find('div.ant-upload-list-item i.anticon-download').length).toBe(0);
wrapper.unmount();
});
it('should support onPreview', () => {
@ -271,6 +290,8 @@ describe('Upload List', () => {
expect(handlePreview).toHaveBeenCalledWith(fileList[0]);
wrapper.find('.anticon-eye').at(1).simulate('click');
expect(handlePreview).toHaveBeenCalledWith(fileList[1]);
wrapper.unmount();
});
it('should support onRemove', async () => {
@ -292,6 +313,8 @@ describe('Upload List', () => {
expect(handleRemove).toHaveBeenCalledWith(fileList[1]);
await sleep();
expect(handleChange.mock.calls.length).toBe(2);
wrapper.unmount();
});
it('should support onDownload', async () => {
@ -316,6 +339,8 @@ describe('Upload List', () => {
</Upload>,
);
wrapper.find('.anticon-download').at(0).simulate('click');
wrapper.unmount();
});
it('should support no onDownload', async () => {
@ -338,6 +363,8 @@ describe('Upload List', () => {
</Upload>,
);
wrapper.find('.anticon-download').at(0).simulate('click');
wrapper.unmount();
});
describe('should generate thumbUrl from file', () => {
@ -360,7 +387,7 @@ describe('Upload List', () => {
delete newFile.thumbUrl;
newFileList.push(newFile);
const ref = React.createRef();
mount(
const wrapper = mount(
<Upload
ref={ref}
listType="picture-card"
@ -370,7 +397,7 @@ describe('Upload List', () => {
<button type="button">upload</button>
</Upload>,
);
ref.current.forceUpdate();
wrapper.update();
await sleep();
expect(ref.current.fileList[2].thumbUrl).not.toBe(undefined);
@ -383,6 +410,8 @@ describe('Upload List', () => {
} else {
expect(offsetY === 0).toBeTruthy();
}
wrapper.unmount();
});
});
});
@ -462,6 +491,8 @@ describe('Upload List', () => {
</Upload>,
);
expect(wrapper.render()).toMatchSnapshot();
wrapper.unmount();
});
it('should support showRemoveIcon and showPreviewIcon', () => {
@ -493,6 +524,8 @@ describe('Upload List', () => {
</Upload>,
);
expect(wrapper.render()).toMatchSnapshot();
wrapper.unmount();
});
it('should support custom onClick in custom icon', async () => {
@ -525,6 +558,8 @@ describe('Upload List', () => {
expect(myClick).toHaveBeenCalled();
await sleep();
expect(handleChange.mock.calls.length).toBe(2);
wrapper.unmount();
});
it('should support removeIcon and downloadIcon', () => {
@ -574,6 +609,9 @@ describe('Upload List', () => {
</Upload>,
);
expect(wrapper2.render()).toMatchSnapshot();
wrapper.unmount();
wrapper2.unmount();
});
// https://github.com/ant-design/ant-design/issues/7762
@ -626,19 +664,22 @@ describe('Upload List', () => {
wrapper.find(Form).simulate('submit');
await sleep();
expect(formRef.getFieldError(['file'])).toEqual([]);
wrapper.unmount();
});
it('return when prop onPreview not exists', () => {
const ref = React.createRef();
mount(<UploadList ref={ref} />);
const wrapper = mount(<UploadList ref={ref} />);
expect(ref.current.handlePreview()).toBe(undefined);
wrapper.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();
mount(
const wrapper = mount(
<UploadList
ref={ref}
items={items}
@ -647,6 +688,7 @@ describe('Upload List', () => {
/>,
);
expect(ref.current.handleDownload(file)).toBe(undefined);
wrapper.unmount();
});
it('previewFile should work correctly', async () => {
@ -655,7 +697,9 @@ describe('Upload List', () => {
const wrapper = mount(
<UploadList listType="picture-card" items={items} locale={{ previewFile: '' }} />,
);
return wrapper.props().previewFile(file);
expect(wrapper.props().previewFile(file)).toBeTruthy();
wrapper.unmount();
});
it('downloadFile should work correctly', async () => {
@ -670,7 +714,11 @@ describe('Upload List', () => {
showUploadList={{ showDownloadIcon: true }}
/>,
);
return wrapper.props().onDownload(file);
// Not throw
wrapper.props().onDownload(file);
wrapper.unmount();
});
it('extname should work correctly when url not exists', () => {
@ -679,6 +727,7 @@ describe('Upload List', () => {
<UploadList listType="picture-card" items={items} locale={{ previewFile: '' }} />,
);
expect(wrapper.find('.ant-upload-list-item-thumbnail').length).toBe(1);
wrapper.unmount();
});
it('extname should work correctly when url exists', done => {
@ -688,6 +737,7 @@ describe('Upload List', () => {
listType="picture"
onDownload={file => {
expect(file.url).toBe('/example');
wrapper.unmount();
done();
}}
items={items}
@ -705,6 +755,8 @@ describe('Upload List', () => {
);
expect(wrapper.find('.ant-upload-list-item-thumbnail').length).toBe(1);
expect(wrapper.find('.ant-upload-list-item-thumbnail').text()).toBe('uploading');
wrapper.unmount();
});
it('onPreview should be called, when url exists', () => {
@ -725,6 +777,8 @@ describe('Upload List', () => {
wrapper.setProps({ items: [{ thumbUrl: 'thumbUrl', uid: 'upload-list-item' }] });
wrapper.find('.ant-upload-list-item-name').simulate('click');
expect(onPreview).toHaveBeenCalled();
wrapper.unmount();
});
it('upload image file should be converted to the base64', async () => {
@ -735,12 +789,14 @@ describe('Upload List', () => {
const wrapper = mount(
<UploadList listType="picture-card" items={fileList} locale={{ uploading: 'uploading' }} />,
);
return wrapper
await wrapper
.props()
.previewFile(mockFile)
.then(dataUrl => {
expect(dataUrl).toEqual('data:image/png;base64,');
});
wrapper.unmount();
});
it("upload non image file shouldn't be converted to the base64", async () => {
@ -751,12 +807,14 @@ describe('Upload List', () => {
const wrapper = mount(
<UploadList listType="picture-card" items={fileList} locale={{ uploading: 'uploading' }} />,
);
return wrapper
await wrapper
.props()
.previewFile(mockFile)
.then(dataUrl => {
expect(dataUrl).toBe('');
});
wrapper.unmount();
});
describe('customize previewFile support', () => {
@ -775,12 +833,14 @@ describe('Upload List', () => {
<button type="button">button</button>
</Upload>,
);
ref.current.forceUpdate();
wrapper.update();
expect(previewFile).toHaveBeenCalledWith(file.originFileObj);
await sleep(100);
wrapper.update();
expect(wrapper.find('.ant-upload-list-item-thumbnail img').prop('src')).toBe(mockThumbnail);
wrapper.unmount();
});
}
test('File', () => new File([], 'xxx.png'));
@ -808,6 +868,8 @@ describe('Upload List', () => {
);
const imgNode = wrapper.find('.ant-upload-list-item-thumbnail img');
expect(imgNode.length).toBe(2);
wrapper.unmount();
});
it('should render <img /> when custom imageUrl return true', () => {
const isImageUrl = jest.fn(() => true);
@ -819,6 +881,8 @@ describe('Upload List', () => {
const imgNode = wrapper.find('.ant-upload-list-item-thumbnail img');
expect(isImageUrl).toHaveBeenCalled();
expect(imgNode.length).toBe(3);
wrapper.unmount();
});
it('should not render <img /> when custom imageUrl return false', () => {
const isImageUrl = jest.fn(() => false);
@ -830,6 +894,8 @@ describe('Upload List', () => {
const imgNode = wrapper.find('.ant-upload-list-item-thumbnail img');
expect(isImageUrl).toHaveBeenCalled();
expect(imgNode.length).toBe(0);
wrapper.unmount();
});
});
@ -897,9 +963,13 @@ describe('Upload List', () => {
});
const afterImgNode = wrapper.find('.ant-upload-list-item-thumbnail img');
expect(afterImgNode.length).toBeTruthy();
wrapper.unmount();
});
it('should not render <img /> when upload non-image file without thumbUrl in onChange', done => {
global.testName =
'should not render <img /> when upload non-image file without thumbUrl in onChange';
let wrapper;
const onChange = async ({ fileList: files }) => {
wrapper.setProps({ fileList: files });
@ -907,6 +977,7 @@ describe('Upload List', () => {
await sleep();
const imgNode = wrapper.find('.ant-upload-list-item-thumbnail img');
expect(imgNode.length).toBe(0);
done();
};
wrapper = mount(
@ -927,14 +998,17 @@ describe('Upload List', () => {
});
it('[deprecated] should support transformFile', done => {
let wrapper;
const handleTransformFile = jest.fn();
const onChange = ({ file }) => {
if (file.status === 'done') {
expect(handleTransformFile).toHaveBeenCalled();
wrapper.unmount();
done();
}
};
const wrapper = mount(
wrapper = mount(
<Upload
action="http://jsonplaceholder.typicode.com/posts/"
transformFile={handleTransformFile}
@ -972,6 +1046,8 @@ describe('Upload List', () => {
expect(wrapper.exists('.ant-upload-list button.trigger')).toBe(true);
wrapper.setProps({ showUploadList: false });
expect(wrapper.exists('.ant-upload-list button.trigger')).toBe(false);
wrapper.unmount();
});
// https://github.com/ant-design/ant-design/issues/26536
@ -998,7 +1074,7 @@ describe('Upload List', () => {
);
};
mount(<MyUpload />);
const wrapper = mount(<MyUpload />);
// Mock async update in a frame
const files = ['light', 'bamboo', 'little'];
@ -1018,6 +1094,8 @@ describe('Upload List', () => {
jest.runAllTimers();
expect(uploadRef.current.fileList).toHaveLength(files.length);
wrapper.unmount();
jest.useRealTimers();
});
@ -1035,5 +1113,7 @@ describe('Upload List', () => {
};
const wrapper = mount(<UploadList locale={{}} items={fileList} itemRender={itemRender} />);
expect(wrapper.render()).toMatchSnapshot();
wrapper.unmount();
});
});

View File

@ -7,11 +7,11 @@ title:
## zh-CN
`beforeUpload` 返回 `false``Promise.reject` 时,只用于拦截上传行为,不会阻止文件进入上传列表([原因](https://github.com/ant-design/ant-design/issues/15561#issuecomment-475108235))。如果需要阻止列表展现,可以参照此例配合 `onChange` 进行实现。
`beforeUpload` 返回 `false``Promise.reject` 时,只用于拦截上传行为,不会阻止文件进入上传列表([原因](https://github.com/ant-design/ant-design/issues/15561#issuecomment-475108235))。如果需要阻止列表展现,可以通过返回 `Upload.LIST_IGNORE` 实现。
## en-US
`beforeUpload` only prevent upload behavior when return false or reject promise, the prevented file would still show in file list. Here is the example you can keep prevented files out of list by using `onChange`.
`beforeUpload` only prevent upload behavior when return false or reject promise, the prevented file would still show in file list. Here is the example you can keep prevented files out of list by return `UPLOAD.LIST_IGNORE`.
```jsx
import React, { useState } from 'react';
@ -19,19 +19,15 @@ import { Upload, Button, message } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
const Uploader = () => {
const [fileList, updateFileList] = useState([]);
const props = {
fileList,
beforeUpload: file => {
if (file.type !== 'image/png') {
message.error(`${file.name} is not a png file`);
}
return file.type === 'image/png';
return file.type === 'image/png' ? true : Upload.LIST_IGNORE;
},
onChange: info => {
console.log(info.fileList);
// file.status is empty when beforeUpload return false
updateFileList(info.fileList.filter(file => !!file.status));
},
};
return (

View File

@ -37,7 +37,7 @@ export interface UploadFile<T = any> {
export interface UploadChangeParam<T extends object = UploadFile> {
// https://github.com/ant-design/ant-design/issues/14420
file: T;
fileList: Array<UploadFile>;
fileList: UploadFile[];
event?: { percent: number };
}

View File

@ -1,56 +0,0 @@
import { useRef, useEffect } from 'react';
import raf from 'rc-util/lib/raf';
import useForceUpdate from '../_util/hooks/useForceUpdate';
// Note. Only for upload usage. Do not export to global util hooks
export default function useFreshState<T>(
defaultValue: T,
propValue?: T,
): [(displayValue?: boolean) => T, (newValue: T) => void] {
const valueRef = useRef(defaultValue);
const forceUpdate = useForceUpdate();
const rafRef = useRef<number>();
// Set value
function setValue(newValue: T) {
valueRef.current = newValue;
forceUpdate();
}
function cleanUp() {
raf.cancel(rafRef.current!);
}
function rafSyncValue(newValue: T) {
cleanUp();
rafRef.current = raf(() => {
setValue(newValue);
});
}
// Get value
function getValue(displayValue = false) {
if (displayValue) {
return propValue || valueRef.current;
}
return valueRef.current;
}
// Effect will always update in a next frame to avoid sync state overwrite current processing state
useEffect(() => {
if (propValue) {
rafSyncValue(propValue);
}
}, [propValue]);
// Clean up
useEffect(
() => () => {
cleanUp();
},
[],
);
return [getValue, setValue];
}

View File

@ -4,11 +4,13 @@ export function T() {
return true;
}
// Fix IE file.status problem
// via coping a new Object
export function fileToObject(file: RcFile): UploadFile {
return {
...file,
/**
* Wrap file with Proxy to provides more info. Will fallback to object if Proxy not support.
*
* Origin comment: Fix IE file.status problem via coping a new Object
*/
export function wrapFile(file: RcFile | UploadFile): UploadFile {
const filledProps = {
lastModified: file.lastModified,
lastModifiedDate: file.lastModifiedDate,
name: file.name,
@ -17,6 +19,41 @@ export function fileToObject(file: RcFile): UploadFile {
uid: file.uid,
percent: 0,
originFileObj: file,
};
if (typeof Proxy !== 'undefined') {
const data = new Map<string | symbol, any>(Object.entries(filledProps));
return new Proxy(file, {
get(target, key) {
if (data.has(key)) {
return data.get(key);
}
return (target as any)[key];
},
set(_, key, value) {
data.set(key, value);
return true;
},
has(target, prop) {
return data.has(prop) || prop in target;
},
ownKeys(target) {
const keys = [...Object.keys(target), ...data.keys()];
return [...new Set(keys)];
},
getOwnPropertyDescriptor() {
return {
enumerable: true,
configurable: true,
};
},
});
}
return {
...file,
...filledProps,
} as UploadFile;
}

View File

@ -147,7 +147,7 @@
"rc-tree": "~4.1.0",
"rc-tree-select": "~4.3.0",
"rc-trigger": "^5.2.1",
"rc-upload": "~3.3.4",
"rc-upload": "~4.0.0-alpha.6",
"rc-util": "^5.8.1",
"scroll-into-view-if-needed": "^2.2.25",
"warning": "^4.0.3"