mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-11-30 02:59:04 +08:00
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:
parent
5d22b0ef7a
commit
79f8fece4b
@ -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 = {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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 (
|
||||
|
@ -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 };
|
||||
}
|
||||
|
||||
|
@ -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];
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user