Browse Source

chore: merge master into feature (#26513)

pull/26767/head
xrkffgg 4 years ago
committed by GitHub
parent
commit
4a4b97c197
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      CHANGELOG.en-US.md
  2. 23
      CHANGELOG.zh-CN.md
  3. 2
      components/checkbox/index.en-US.md
  4. 78
      components/config-provider/__tests__/form-locale.test.js
  5. 43
      components/config-provider/__tests__/locale.test.js
  6. 2
      components/config-provider/index.zh-CN.md
  7. 7
      components/grid/style/rtl.less
  8. 20
      components/modal/index.en-US.md
  9. 21
      components/modal/index.zh-CN.md
  10. 59
      components/upload/Upload.tsx
  11. 4
      components/upload/__tests__/__snapshots__/uploadlist.test.js.snap
  12. 63
      components/upload/__tests__/upload.test.js
  13. 18
      components/upload/__tests__/uploadlist.test.js
  14. 4
      package.json

23
CHANGELOG.en-US.md

@ -15,6 +15,29 @@ timeline: true
---
## 4.6.2
`2020-08-31`
- Upload
- 🐞 Fix Upload list status issue when in control mode. [#26481](https://github.com/ant-design/ant-design/pull/26481)
- 💄 Fix Upload `picture-card` style unexpected margin in Form.Item. [#26367](https://github.com/ant-design/ant-design/pull/26367)
- 💄 Fix Select focus shadow style. [#26465](https://github.com/ant-design/ant-design/pull/26465) [@Rainy](https://github.com/Rainy)
- Table
- 🐞 Fix Table Pagination not hide with empty data when show on top position. [#26143](https://github.com/ant-design/ant-design/pull/26143) [@zhangchen915](https://github.com/zhangchen915)
- 💄 Fix Table expand icon size issue when `@font-size-base` is `12px`. [#26409](https://github.com/ant-design/ant-design/pull/26409)
- Space
- 🐞 Fix Space not support React.Fragment issue. [#26444](https://github.com/ant-design/ant-design/pull/26444)
- 🐞 Fix Space preserve empty dom node when `children` contains empty node. [#26389](https://github.com/ant-design/ant-design/pull/26389)
- 🐞 Fix Badge not work when `status` or `color` is empty. [#26375](https://github.com/ant-design/ant-design/pull/26375) [@zhangchen915](https://github.com/zhangchen915)
- 💄 Fix Tree draggable transition style. [#26387](https://github.com/ant-design/ant-design/pull/26387)
- 🐞 Fix `colorPalette is not defined` when customize theme in some situation. [#26395](https://github.com/ant-design/ant-design/pull/26395)
- TypeScript
- 🐞 Fix TimePicker.RangePicker typescript need `picker` issue. [#26446](https://github.com/ant-design/ant-design/pull/26446)
- 🐞 Upload extended `showUploadList` of Upload with `removeIcon` and `downloadIcon` properties. [#26406](https://github.com/ant-design/ant-design/pull/26406) [@bencallaway](https://github.com/bencallaway)
- RTL
- 🐞 Fix the rtl style of Col. [#26479](https://github.com/ant-design/ant-design/pull/26479) [#26482](https://github.com/ant-design/ant-design/pull/26482) [@TrueMoein](https://github.com/TrueMoein)
## 4.6.1
`2020-08-24`

23
CHANGELOG.zh-CN.md

@ -15,6 +15,29 @@ timeline: true
---
## 4.6.2
`2020-08-31`
- Upload
- 🐞 修复 Upload 在受控模式下同步更新导致的状态错误问题。[#26481](https://github.com/ant-design/ant-design/pull/26481)
- 💄 修复 Upload 图片样式在 Form.Item 中有异常 margin 的问题。[#26367](https://github.com/ant-design/ant-design/pull/26367)
- 💄 修复 Select focus 状态样式无阴影。[#26465](https://github.com/ant-design/ant-design/pull/26465) [@Rainy](https://github.com/Rainy)
- Table
- 🐞 修复 Table Pagination 展示于上侧且没有数据时不消失的问题。[#26143](https://github.com/ant-design/ant-design/pull/26143) [@zhangchen915](https://github.com/zhangchen915)
- 💄 修复 Table 展开图标在 `@font-size-base``12px` 时样式错位的问题。[#26409](https://github.com/ant-design/ant-design/pull/26409)
- Space
- 🐞 修复 Space 不支持 React.Fragment 的问题。[#26444](https://github.com/ant-design/ant-design/pull/26444)
- 🐞 修复 Space 在 `children` 中包含空节点时会出现空 dom 的问题。[#26389](https://github.com/ant-design/ant-design/pull/26389)
- 🐞 修复 Badge 在 `status``color` 为空时不展示。[#26375](https://github.com/ant-design/ant-design/pull/26375) [@zhangchen915](https://github.com/zhangchen915)
- 💄 修复 Tree `draggable` 切换时样式 transition 变化的问题。[#26387](https://github.com/ant-design/ant-design/pull/26387)
- 🐞 修复使用主题有时会报 `colorPalette is not defined` 的问题。[#26395](https://github.com/ant-design/ant-design/pull/26395)
- TypeScript
- 🐞 修复 TimePicker.RangePicker 定义需要 `picker` 的问题。[#26446](https://github.com/ant-design/ant-design/pull/26446)
- 🐞 Upload 组件 `showUploadList` 类型添加 `removeIcon``downloadIcon` 属性声明。[#26406](https://github.com/ant-design/ant-design/pull/26406) [@bencallaway](https://github.com/bencallaway)
- RTL
- 🐞 修复 Col RTL 样式。[#26479](https://github.com/ant-design/ant-design/pull/26479) [#26482](https://github.com/ant-design/ant-design/pull/26482) [@TrueMoein](https://github.com/TrueMoein)
## 4.6.1
`2020-08-24`

2
components/checkbox/index.en-US.md

@ -34,7 +34,7 @@ Checkbox component.
| defaultValue | Default selected value | string\[] | \[] | |
| disabled | If disable all checkboxes | boolean | false | |
| name | The `name` property of all `input[type="checkbox"]` children | string | - | |
| options | Specifies options | Array<CheckboxOptionType \| string>[] | \[] | |
| options | Specifies options | string\[] \| Option\[] | \[] | |
| value | Used for setting the currently selected value | string\[] | \[] | |
| onChange | The callback function that is triggered when the state changes | function(checkedValue) | - | |

78
components/config-provider/__tests__/form-locale.test.js

@ -0,0 +1,78 @@
import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import ConfigProvider from '..';
import zhCN from '../../locale/zh_CN';
import Form from '../../form';
describe('ConfigProvider.Form.Locale', () => {
describe('form validateMessages', () => {
const wrapperComponent = ({ validateMessages }) => {
const formRef = React.createRef();
const wrapper = mount(
<ConfigProvider locale={zhCN} form={{ validateMessages }}>
<Form ref={formRef} initialValues={{ age: 18 }}>
<Form.Item name="test" label="姓名" rules={[{ required: true }]}>
<input />
</Form.Item>
<Form.Item name="age" label="年龄" rules={[{ type: 'number', len: 17 }]}>
<input />
</Form.Item>
</Form>
</ConfigProvider>,
);
return [wrapper, formRef];
};
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('set locale zhCN', async () => {
const [wrapper, formRef] = wrapperComponent({});
await act(async () => {
try {
await formRef.current.validateFields();
} catch (e) {
// Do nothing
}
});
await act(async () => {
jest.runAllTimers();
wrapper.update();
await Promise.resolve();
});
expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('请输入姓名');
});
it('set locale zhCN and set form validateMessages one item, other use default message', async () => {
const [wrapper, formRef] = wrapperComponent({ validateMessages: { required: '必须' } });
await act(async () => {
try {
await formRef.current.validateFields();
} catch (e) {
// Do nothing
}
});
await act(async () => {
jest.runAllTimers();
wrapper.update();
await Promise.resolve();
});
expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('必须');
expect(wrapper.find('.ant-form-item-explain').last().text()).toEqual('年龄必须等于17');
});
});
});

43
components/config-provider/__tests__/locale.test.js

@ -6,12 +6,6 @@ import zhCN from '../../locale/zh_CN';
import enUS from '../../locale/en_US';
import TimePicker from '../../time-picker';
import Modal from '../../modal';
import Form from '../../form';
const delay = (timeout = 0) =>
new Promise(resolve => {
setTimeout(resolve, timeout);
});
describe('ConfigProvider.Locale', () => {
function $$(className) {
@ -113,41 +107,4 @@ describe('ConfigProvider.Locale', () => {
testLocale(wrapper);
});
});
describe('form validateMessages', () => {
const wrapperComponent = ({ validateMessages }) =>
mount(
<ConfigProvider locale={zhCN} form={{ validateMessages }}>
<Form initialValues={{ age: 18 }}>
<Form.Item name="test" label="姓名" rules={[{ required: true }]}>
<input />
</Form.Item>
<Form.Item name="age" label="年龄" rules={[{ type: 'number', len: 17 }]}>
<input />
</Form.Item>
</Form>
</ConfigProvider>,
);
it('set locale zhCN', async () => {
const wrapper = wrapperComponent({});
wrapper.find('form').simulate('submit');
await delay(50);
wrapper.update();
expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('请输入姓名');
});
it('set locale zhCN and set form validateMessages one item, other use default message', async () => {
const wrapper = wrapperComponent({ validateMessages: { required: '必须' } });
wrapper.find('form').simulate('submit');
await delay(200);
wrapper.update();
expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('必须');
expect(wrapper.find('.ant-form-item-explain').last().text()).toEqual('年龄必须等于17');
});
});
});

2
components/config-provider/index.zh-CN.md

@ -48,7 +48,7 @@ export default () => (
| getPopupContainer | 弹出框(Select, Tooltip, Menu 等等)渲染父节点,默认渲染到 body 上。 | function(triggerNode) | () => document.body | |
| getTargetContainer | 配置 Affix、Anchor 滚动监听容器。 | () => HTMLElement | () => window | 4.2.0 |
| locale | 语言包配置,语言包可到 [antd/es/locale](http://unpkg.com/antd/es/locale/) 目录下寻找 | object | - | |
| prefixCls | 设置统一样式前缀。`注意:需要配合 less 变量 [@ant-prefix](https://github.com/ant-design/ant-design/blob/2c6c789e3a9356f96c47aea0083f5a15538315cf/components/style/themes/default.less#L7) 使用` | string | `ant` | |
| prefixCls | 设置统一样式前缀。注意:需要配合 `less` 变量 [@ant-prefix](https://github.com/ant-design/ant-design/blob/2c6c789e3a9356f96c47aea0083f5a15538315cf/components/style/themes/default.less#L7) 使用 | string | `ant` | |
| pageHeader | 统一设置 PageHeader 的 ghost,参考 [PageHeader](/components/page-header) | { ghost: boolean } | true | |
| direction | 设置文本展示方向。 [示例](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | |
| space | 设置 Space 的 `size`,参考 [Space](/components/space) | { size: `small` \| `middle` \| `large` \| `number` } | - | 4.1.0 |

7
components/grid/style/rtl.less

@ -7,13 +7,6 @@
}
}
.@{ant-prefix}-col {
&&-rtl {
float: right;
width: 100%;
}
}
// mixin
.loop-grid-columns(@index, @class) when (@index > 0) {
.@{ant-prefix}-col@{class}-push-@{index} {

20
components/modal/index.en-US.md

@ -18,6 +18,7 @@ When requiring users to interact with the application, but without jumping to a
| afterClose | Specify a function that will be called when modal is closed completely | function | - |
| bodyStyle | Body style for modal body element. Such as height, padding etc | CSSProperties | {} |
| cancelText | Text of the Cancel button | string \| ReactNode | `Cancel` |
| cancelButtonProps | The cancel button props | [ButtonProps](/components/button/#API) | - |
| centered | Centered Modal | boolean | false |
| closable | Whether a close (x) button is visible on top right of the modal dialog or not | boolean | true |
| closeIcon | Custom close icon | ReactNode | &lt;CloseOutlined /> |
@ -26,21 +27,21 @@ When requiring users to interact with the application, but without jumping to a
| footer | Footer content, set as `footer={null}` when you don't need default buttons | string \| ReactNode | (OK and Cancel buttons) |
| forceRender | Force render Modal | boolean | false |
| getContainer | Return the mount node for Modal | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body |
| keyboard | Whether support press esc to close | boolean | true |
| mask | Whether show mask or not | boolean | true |
| maskClosable | Whether to close the modal dialog when the mask (area outside the modal) is clicked | boolean | true |
| maskStyle | Style for modal's mask element | object | {} |
| okButtonProps | The ok button props | [ButtonProps](/components/button/#API) | - |
| okText | Text of the OK button | string \| ReactNode | `OK` |
| okType | Button `type` of the OK button | string | `primary` |
| okButtonProps | The ok button props | [ButtonProps](/components/button/#API) | - |
| cancelButtonProps | The cancel button props | [ButtonProps](/components/button/#API) | - |
| onCancel | Specify a function that will be called when a user clicks mask, close button on top right or Cancel button | function(e) | - |
| onOk | Specify a function that will be called when a user clicks the OK button | function(e) | - |
| style | Style of floating layer, typically used at least for adjusting the position | CSSProperties | - |
| title | The modal dialog's title | string \| ReactNode | - |
| visible | Whether the modal dialog is visible or not | boolean | false |
| width | Width of the modal dialog | string \| number | 520 |
| wrapClassName | The class name of the container of the modal dialog | string | - |
| zIndex | The `z-index` of the Modal | number | 1000 |
| onCancel | Specify a function that will be called when a user clicks mask, close button on top right or Cancel button | function(e) | - |
| onOk | Specify a function that will be called when a user clicks the OK button | function(e) | - |
#### Note
@ -63,23 +64,26 @@ The items listed above are all functions, expecting a settings object as paramet
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| autoFocusButton | Specify which button to autofocus | null \| `ok` \| `cancel` | `ok` | |
| cancelButtonProps | The cancel button props | [ButtonProps](/components/button/#API) | - | |
| cancelText | Text of the Cancel button with Modal.confirm | string | `Cancel` | |
| centered | Centered Modal | boolean | false | |
| className | The className of container | string | - | |
| content | Content | string \| ReactNode | - | |
| getContainer | Return the mount node for Modal | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |
| icon | Custom icon | ReactNode | &lt;QuestionCircle /> | 3.12.0 |
| keyboard | Whether support press esc to close | boolean | true | |
| mask | Whether show mask or not. | boolean | true | |
| maskClosable | Whether to close the modal dialog when the mask (area outside the modal) is clicked | boolean | false | |
| maskStyle | Style for modal's mask element | object | {} | |
| okButtonProps | The ok button props | [ButtonProps](/components/button/#API) | - | |
| okText | Text of the OK button | string | `OK` | |
| okType | Button `type` of the OK button | string | `primary` | |
| okButtonProps | The ok button props | [ButtonProps](/components/button/#API) | - | |
| cancelButtonProps | The cancel button props | [ButtonProps](/components/button/#API) | - | |
| onCancel | Specify a function that will be called when the user clicks the Cancel button. The parameter of this function is a function whose execution should include closing the dialog. You can also just return a promise and when the promise is resolved, the modal dialog will also be closed | function(close) | - | |
| onOk | Specify a function that will be called when the user clicks the OK button. The parameter of this function is a function whose execution should include closing the dialog. You can also just return a promise and when the promise is resolved, the modal dialog will also be closed | function(close) | - | |
| style | Style of floating layer, typically used at least for adjusting the position | CSSProperties | - | |
| title | Title | string \| ReactNode | - | |
| width | Width of the modal dialog | string \| number | 416 | |
| zIndex | The `z-index` of the Modal | number | 1000 | |
| onCancel | Specify a function that will be called when the user clicks the Cancel button. The parameter of this function is a function whose execution should include closing the dialog. You can also just return a promise and when the promise is resolved, the modal dialog will also be closed | function(close) | - | |
| onOk | Specify a function that will be called when the user clicks the OK button. The parameter of this function is a function whose execution should include closing the dialog. You can also just return a promise and when the promise is resolved, the modal dialog will also be closed | function(close) | - | |
All the `Modal.method`s will return a reference, and then we can update and close the modal dialog by the reference.

21
components/modal/index.zh-CN.md

@ -20,6 +20,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/3StSdUlSH/Modal.svg
| --- | --- | --- | --- |
| afterClose | Modal 完全关闭后的回调 | function | - |
| bodyStyle | Modal body 样式 | object | {} |
| cancelButtonProps | cancel 按钮 props | [ButtonProps](/components/button/#API) | - |
| cancelText | 取消按钮文字 | string \| ReactNode | `取消` |
| centered | 垂直居中展示 Modal | boolean | false |
| closable | 是否显示右上角的关闭按钮 | boolean | true |
@ -33,18 +34,17 @@ cover: https://gw.alipayobjects.com/zos/alicdn/3StSdUlSH/Modal.svg
| mask | 是否展示遮罩 | boolean | true |
| maskClosable | 点击蒙层是否允许关闭 | boolean | true |
| maskStyle | 遮罩样式 | object | {} |
| okButtonProps | ok 按钮 props | [ButtonProps](/components/button/#API) | - |
| okText | 确认按钮文字 | string \| ReactNode | `确定` |
| okType | 确认按钮类型 | string | `primary` |
| okButtonProps | ok 按钮 props | [ButtonProps](/components/button/#API) | - |
| cancelButtonProps | cancel 按钮 props | [ButtonProps](/components/button/#API) | - |
| onCancel | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | - |
| onOk | 点击确定回调 | function(e) | - |
| style | 可用于设置浮层的样式,调整浮层位置等 | CSSProperties | - |
| title | 标题 | string \| ReactNode | - |
| visible | 对话框是否可见 | boolean | - |
| width | 宽度 | string \| number | 520 |
| wrapClassName | 对话框外层容器的类名 | string | - |
| zIndex | 设置 Modal 的 `z-index` | number | 1000 |
| onCancel | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | - |
| onOk | 点击确定回调 | function(e) | - |
#### 注意
@ -67,21 +67,26 @@ cover: https://gw.alipayobjects.com/zos/alicdn/3StSdUlSH/Modal.svg
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| autoFocusButton | 指定自动获得焦点的按钮 | null \| `ok` \| `cancel` | `ok` | |
| cancelButtonProps | cancel 按钮 props | [ButtonProps](/components/button/#API) | - | |
| cancelText | 设置 Modal.confirm 取消按钮文字 | string | `取消` | |
| centered | 垂直居中展示 Modal | boolean | false | |
| className | 容器类名 | string | - | |
| content | 内容 | string \| ReactNode | - | |
| getContainer | 指定 Modal 挂载的 HTML 节点, false 为挂载在当前 dom | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |
| icon | 自定义图标 | ReactNode | &lt;QuestionCircle /> | 3.12.0 |
| keyboard | 是否支持键盘 esc 关闭 | boolean | true | |
| mask | 是否展示遮罩 | boolean | true | |
| maskClosable | 点击蒙层是否允许关闭 | boolean | false | |
| maskStyle | 遮罩样式 | object | {} | |
| okButtonProps | ok 按钮 props | [ButtonProps](/components/button/#API) | - | |
| okText | 确认按钮文字 | string | `确定` | |
| okType | 确认按钮类型 | string | `primary` | |
| okButtonProps | ok 按钮 props | [ButtonProps](/components/button/#API) | - | |
| cancelButtonProps | cancel 按钮 props | [ButtonProps](/components/button/#API) | - | |
| onCancel | 取消回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function(close) | - | |
| onOk | 点击确定回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function(close) | - | |
| style | 可用于设置浮层的样式,调整浮层位置等 | CSSProperties | - | |
| title | 标题 | string \| ReactNode | - | |
| width | 宽度 | string \| number | 416 | |
| zIndex | 设置 Modal 的 `z-index` | number | 1000 | |
| onCancel | 取消回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function(close) | - | |
| onOk | 点击确定回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function(close) | - | |
以上函数调用后,会返回一个引用,可以通过该引用更新和关闭弹窗。

59
components/upload/Upload.tsx

@ -18,7 +18,6 @@ import LocaleReceiver from '../locale-provider/LocaleReceiver';
import defaultLocale from '../locale/default';
import { ConfigContext } from '../config-provider';
import devWarning from '../_util/devWarning';
import useSyncState from '../_util/hooks/useSyncState';
import useForceUpdate from '../_util/hooks/useForceUpdate';
export { UploadProps };
@ -45,15 +44,18 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
style,
} = props;
const [getFileList, setFileList] = useSyncState<Array<UploadFile>>(
fileListProp || defaultFileList || [],
);
const [dragState, setDragState] = React.useState<string>('drop');
const forceUpdate = useForceUpdate();
// `fileListRef` used for internal state sync to avoid control mode set it back when sync update.
// `visibleFileList` used for display in UploadList instead.
// It's a workaround and not the best solution.
const fileListRef = React.useRef(fileListProp || defaultFileList || []);
const visibleFileList = fileListProp || fileListRef.current;
const upload = React.useRef<any>();
React.useEffect(() => {
setFileList(fileListProp || defaultFileList || []);
devWarning(
'fileList' in props || !('value' in props),
'Upload',
@ -62,15 +64,15 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
}, []);
React.useEffect(() => {
if ('fileList' in props) {
setFileList(fileListProp || []);
if (fileListProp !== undefined && fileListProp !== fileListRef.current) {
fileListRef.current = fileListProp;
forceUpdate();
}
}, [fileListProp]);
const onChange = (info: UploadChangeParam) => {
if (!('fileList' in props)) {
setFileList(info.fileList);
}
fileListRef.current = info.fileList;
forceUpdate();
const { onChange: onChangeProp } = props;
if (onChangeProp) {
@ -85,7 +87,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
const targetItem = fileToObject(file);
targetItem.status = 'uploading';
const nextFileList = getFileList().concat();
const nextFileList = fileListRef.current.concat();
const fileIndex = nextFileList.findIndex(({ uid }: UploadFile) => uid === targetItem.uid);
if (fileIndex === -1) {
@ -108,7 +110,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
} catch (e) {
/* do nothing */
}
const targetItem = getFileItem(file, getFileList());
const targetItem = getFileItem(file, fileListRef.current);
// removed
if (!targetItem) {
return;
@ -118,12 +120,12 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
targetItem.xhr = xhr;
onChange({
file: { ...targetItem },
fileList: getFileList().concat(),
fileList: fileListRef.current.concat(),
});
};
const onProgress = (e: { percent: number }, file: UploadFile) => {
const targetItem = getFileItem(file, getFileList());
const targetItem = getFileItem(file, fileListRef.current);
// removed
if (!targetItem) {
return;
@ -132,12 +134,12 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
onChange({
event: e,
file: { ...targetItem },
fileList: getFileList().concat(),
fileList: fileListRef.current.concat(),
});
};
const onError = (error: Error, response: any, file: UploadFile) => {
const targetItem = getFileItem(file, getFileList());
const targetItem = getFileItem(file, fileListRef.current);
// removed
if (!targetItem) {
return;
@ -147,7 +149,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
targetItem.status = 'error';
onChange({
file: { ...targetItem },
fileList: getFileList().concat(),
fileList: fileListRef.current.concat(),
});
};
@ -158,7 +160,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
return;
}
const removedFileList = removeFileItem(file, getFileList());
const removedFileList = removeFileItem(file, fileListRef.current);
if (removedFileList) {
file.status = 'removed';
@ -187,13 +189,11 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
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);
}
});
fileListRef.current.concat(fileListArgs.map(fileToObject)).forEach(f => {
if (uniqueList.every(uf => uf.uid !== f.uid)) {
uniqueList.push(f);
}
});
onChange({
file,
@ -207,13 +207,12 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
return true;
};
// Test needs
const forceUpdate = useForceUpdate();
React.useImperativeHandle(ref, () => ({
onStart,
onSuccess,
onProgress,
onError,
fileList: getFileList(),
fileList: fileListRef.current,
upload: upload.current,
forceUpdate,
}));
@ -252,7 +251,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
return (
<UploadList
listType={listType}
items={getFileList()}
items={visibleFileList}
previewFile={previewFile}
onPreview={onPreview}
onDownload={onDownload}
@ -280,7 +279,9 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
prefixCls,
{
[`${prefixCls}-drag`]: true,
[`${prefixCls}-drag-uploading`]: getFileList().some(file => file.status === 'uploading'),
[`${prefixCls}-drag-uploading`]: fileListRef.current.some(
file => file.status === 'uploading',
),
[`${prefixCls}-drag-hover`]: dragState === 'dragover',
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-rtl`]: direction === 'rtl',

4
components/upload/__tests__/__snapshots__/uploadlist.test.js.snap

@ -28,7 +28,7 @@ exports[`Upload List handle error 1`] = `
class="ant-upload-list ant-upload-list-text"
>
<div
class="ant-upload-animate-enter"
class=""
>
<div
class="ant-upload-list-item ant-upload-list-item-error ant-upload-list-item-list-type-text"
@ -262,7 +262,7 @@ exports[`Upload List should be uploading when upload a file 2`] = `
class="ant-upload-list ant-upload-list-text"
>
<div
class="ant-upload-animate-enter"
class=""
>
<span>
<div

63
components/upload/__tests__/upload.test.js

@ -1,6 +1,7 @@
/* eslint-disable react/no-string-refs, react/prefer-es6-class */
import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import Upload from '..';
import Form from '../../form';
import { T, fileToObject, getFileItem, removeFileItem } from '../utils';
@ -554,4 +555,66 @@ describe('Upload', () => {
wrapper.find('.ant-upload').at(1).simulate('mouseLeave');
expect(onMouseLeave).toHaveBeenCalled();
});
// https://github.com/ant-design/ant-design/issues/26427
it('should sync file list with control mode', done => {
let callTimes = 0;
const customRequest = jest.fn(async options => {
options.onProgress({ percent: 0 });
const url = Promise.resolve('https://ant.design');
options.onProgress({ percent: 100 });
options.onSuccess({}, { ...options.file, url });
});
const Demo = () => {
const [fileList, setFileList] = React.useState([]);
const onChange = e => {
const newFileList = Array.isArray(e) ? e : e.fileList;
setFileList(newFileList);
const file = newFileList[0];
callTimes += 1;
switch (callTimes) {
case 1:
case 2:
expect(file).toEqual(expect.objectContaining({ status: 'uploading', percent: 0 }));
break;
case 3:
expect(file).toEqual(expect.objectContaining({ status: 'uploading', percent: 100 }));
break;
case 4:
expect(file).toEqual(expect.objectContaining({ status: 'done', percent: 100 }));
break;
default:
// Do nothing
}
if (callTimes >= 4) {
done();
}
};
return (
<Upload customRequest={customRequest} onChange={onChange} fileList={fileList}>
<button type="button">Upload</button>
</Upload>
);
};
const wrapper = mount(<Demo />);
act(() => {
wrapper.find('input').simulate('change', {
target: {
files: [{ file: 'foo.png' }],
},
});
});
});
});

18
components/upload/__tests__/uploadlist.test.js

@ -1,4 +1,5 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mount } from 'enzyme';
import Upload from '..';
import UploadList from '../UploadList';
@ -801,14 +802,23 @@ describe('Upload List', () => {
const nonImageFile = new File([''], 'foo.7z', { type: 'application/x-7z-compressed' });
it('should render <img /> when upload non-image file and configure thumbUrl in onChange', done => {
let wrapper;
const onChange = async ({ fileList: files }) => {
const newfileList = files.map(item => ({
const onChange = async ({ file, fileList: files }) => {
const newFileList = files.map(item => ({
...item,
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
}));
wrapper.setProps({ fileList: newfileList });
await sleep();
wrapper.setProps({ fileList: newFileList });
if (file.status === 'uploading') {
return;
}
act(async () => {
await sleep();
wrapper.update();
});
const imgNode = wrapper.find('.ant-upload-list-item-thumbnail img');
expect(imgNode.length).toBe(1);
done();

4
package.json

@ -1,6 +1,6 @@
{
"name": "antd",
"version": "4.6.1",
"version": "4.6.2",
"description": "An enterprise-class UI design language and React components implementation",
"title": "Ant Design",
"keywords": [
@ -147,7 +147,7 @@
"rc-tree": "~3.9.0",
"rc-tree-select": "~4.1.1",
"rc-trigger": "~4.4.0",
"rc-upload": "~3.2.0",
"rc-upload": "~3.2.1",
"rc-util": "^5.1.0",
"scroll-into-view-if-needed": "^2.2.25",
"warning": "^4.0.3"

Loading…
Cancel
Save