chore: auto merge branchs (#33889)

chore: merge master into feature
This commit is contained in:
github-actions[bot] 2022-01-29 11:53:19 +00:00 committed by GitHub
commit 0d4e1b1356
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 191 additions and 16 deletions

View File

@ -0,0 +1,89 @@
import React, { useState } from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import Button from '../button';
const specialDelay = 9529;
const Content = () => {
const [loading, setLoading] = useState(false);
const toggleLoading = () => {
setLoading(!loading);
};
const [visible, setVisible] = useState(true);
const toggleVisible = () => {
setVisible(!visible);
};
return (
<div>
<button type="button" id="toggle_loading" onClick={toggleLoading}>
Toggle Loading
</button>
<button type="button" id="toggle_visible" onClick={toggleVisible}>
Toggle Visible
</button>
{visible && <Button type="text" loading={loading ? { delay: specialDelay } : false} />}
</div>
);
};
it('Delay loading timer in Button component', () => {
const otherTimer: any = 9528;
jest.spyOn(window, 'setTimeout').mockReturnValue(otherTimer);
jest.restoreAllMocks();
const wrapper = mount(<Content />);
const btnTimer: any = 9527;
jest.spyOn(window, 'setTimeout').mockReturnValue(btnTimer);
jest.spyOn(window, 'clearTimeout');
const setTimeoutMock = window.setTimeout as any as jest.Mock;
const clearTimeoutMock = window.clearTimeout as any as jest.Mock;
// other component may call setTimeout or clearTimeout
const setTimeoutCount = () => {
const items = setTimeoutMock.mock.calls.filter(item => item[1] === specialDelay);
return items.length;
};
const clearTimeoutCount = () => {
const items = clearTimeoutMock.mock.calls.filter(item => item[0] === btnTimer);
return items.length;
};
// switch loading state to true
wrapper.find('#toggle_loading').at(0).simulate('click');
expect(setTimeoutCount()).toBe(1);
expect(clearTimeoutCount()).toBe(0);
// trigger timer handler
act(() => {
setTimeoutMock.mock.calls[0][0]();
});
expect(setTimeoutCount()).toBe(1);
expect(clearTimeoutCount()).toBe(0);
// switch loading state to false
wrapper.find('#toggle_loading').at(0).simulate('click');
expect(setTimeoutCount()).toBe(1);
expect(clearTimeoutCount()).toBe(0);
// switch loading state to true
wrapper.find('#toggle_loading').at(0).simulate('click');
expect(setTimeoutCount()).toBe(2);
expect(clearTimeoutCount()).toBe(0);
// switch loading state to false
wrapper.find('#toggle_loading').at(0).simulate('click');
expect(setTimeoutCount()).toBe(2);
expect(clearTimeoutCount()).toBe(1);
// switch loading state to true
wrapper.find('#toggle_loading').at(0).simulate('click');
// remove Button component
wrapper.find('#toggle_visible').at(0).simulate('click');
expect(setTimeoutCount()).toBe(3);
expect(clearTimeoutCount()).toBe(2);
jest.restoreAllMocks();
});

View File

@ -156,7 +156,6 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
const [hasTwoCNChar, setHasTwoCNChar] = React.useState(false);
const { getPrefixCls, autoInsertSpaceInButton, direction } = React.useContext(ConfigContext);
const buttonRef = (ref as any) || React.createRef<HTMLElement>();
const delayTimeoutRef = React.useRef<number>();
const isNeedInserted = () =>
React.Children.count(children) === 1 && !icon && !isUnborderedButtonType(type);
@ -181,14 +180,25 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
typeof loading === 'object' && loading.delay ? loading.delay || true : !!loading;
React.useEffect(() => {
clearTimeout(delayTimeoutRef.current);
let delayTimer: number | null = null;
if (typeof loadingOrDelay === 'number') {
delayTimeoutRef.current = window.setTimeout(() => {
delayTimer = window.setTimeout(() => {
delayTimer = null;
setLoading(loadingOrDelay);
}, loadingOrDelay);
} else {
setLoading(loadingOrDelay);
}
return () => {
if (delayTimer) {
// in order to not perform a React state update on an unmounted component
// and clear timer after 'loadingOrDelay' updated.
window.clearTimeout(delayTimer);
delayTimer = null;
}
};
}, [loadingOrDelay]);
React.useEffect(fixTwoCNChar, [buttonRef]);

View File

@ -256,7 +256,7 @@
letter-spacing: 0.34em;
}
&-block {
&&-block {
width: 100%;
}

View File

@ -346,4 +346,22 @@ describe('Tooltip', () => {
);
expect(wrapper.find('.ant-tooltip-inner').getDOMNode().style.color).toBe('red');
});
it('should work with loading switch', () => {
const onVisibleChange = jest.fn();
const wrapper = mount(
<Tooltip
title="loading tips"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onVisibleChange={onVisibleChange}
>
<Switch loading defaultChecked />
</Tooltip>,
);
const wrapperEl = wrapper.find('.ant-tooltip-disabled-compatible-wrapper');
expect(wrapperEl).toHaveLength(1);
wrapper.find('span').first().simulate('mouseenter');
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
});
});

View File

@ -85,10 +85,8 @@ const PresetColorRegex = new RegExp(`^(${PresetColorTypes.join('|')})(-inverse)?
function getDisabledCompatibleChildren(element: React.ReactElement<any>, prefixCls: string) {
const elementType = element.type as any;
if (
(elementType.__ANT_BUTTON === true ||
elementType.__ANT_SWITCH === true ||
element.type === 'button') &&
element.props.disabled
((elementType.__ANT_BUTTON === true || element.type === 'button') && element.props.disabled) ||
(elementType.__ANT_SWITCH === true && (element.props.disabled || element.props.loading))
) {
// Pick some layout related style properties up to span
// Prevent layout bugs like https://github.com/ant-design/ant-design/issues/5254

View File

@ -293,12 +293,14 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
const textEle = typographyRef.current;
if (enableEllipsis && cssEllipsis && textEle) {
const currentEllipsis = textEle.offsetWidth < textEle.scrollWidth;
const currentEllipsis = cssLineClamp
? textEle.offsetHeight < textEle.scrollHeight
: textEle.offsetWidth < textEle.scrollWidth;
if (isNativeEllipsis !== currentEllipsis) {
setIsNativeEllipsis(currentEllipsis);
}
}
}, [enableEllipsis, cssEllipsis, children]);
}, [enableEllipsis, cssEllipsis, children, cssLineClamp]);
// ========================== Tooltip ===========================
const tooltipTitle = ellipsisConfig.tooltip === true ? children : ellipsisConfig.tooltip;

View File

@ -277,4 +277,34 @@ describe('Typography.Ellipsis', () => {
const tooltipWrapper = mount(<Base ellipsis={{ expandable: true, tooltip: 'little' }} />);
expect(tooltipWrapper.find('.ant-typography').prop('aria-label')).toEqual('little');
});
it('should display tooltip if line clamp', () => {
mockRectSpy = spyElementPrototypes(HTMLElement, {
scrollHeight: {
get() {
let html = this.innerHTML;
html = html.replace(/<[^>]*>/g, '');
const lines = Math.ceil(html.length / LINE_STR_COUNT);
return lines * 16;
},
},
offsetHeight: {
get: () => 32,
},
offsetWidth: {
get: () => 100,
},
scrollWidth: {
get: () => 100,
},
});
const wrapper = mount(
<Base ellipsis={{ tooltip: 'This is tooltip', rows: 2 }}>
Ant Design, a design language for background applications, is refined by Ant UED Team.
</Base>,
);
expect(wrapper.find('EllipsisTooltip').prop('isEllipsis')).toBeTruthy();
mockRectSpy.mockRestore();
});
});

View File

@ -322,7 +322,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
delete rcUploadProps.id;
}
const renderUploadList = (button?: React.ReactNode) =>
const renderUploadList = (button?: React.ReactNode, buttonVisible?: boolean) =>
showUploadList ? (
<LocaleReceiver componentName="Upload" defaultLocale={defaultLocale.Upload}>
{(locale: UploadLocale) => {
@ -354,6 +354,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
isImageUrl={isImageUrl}
progress={progress}
appendAction={button}
appendActionVisible={buttonVisible}
itemRender={itemRender}
/>
);
@ -400,8 +401,8 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
[`${prefixCls}-rtl`]: direction === 'rtl',
});
const uploadButton = (
<div className={uploadButtonCls} style={children ? undefined : { display: 'none' }}>
const renderUploadButton = (uploadButtonStyle?: React.CSSProperties) => (
<div className={uploadButtonCls} style={uploadButtonStyle}>
<RcUpload {...rcUploadProps} ref={upload} />
</div>
);
@ -409,14 +410,14 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
if (listType === 'picture-card') {
return (
<span className={classNames(`${prefixCls}-picture-card-wrapper`, className)}>
{renderUploadList(uploadButton)}
{renderUploadList(renderUploadButton(), !!children)}
</span>
);
}
return (
<span className={className}>
{uploadButton}
{renderUploadButton(children ? undefined : { display: 'none' })}
{renderUploadList()}
</span>
);

View File

@ -42,6 +42,7 @@ const InternalUploadList: React.ForwardRefRenderFunction<unknown, UploadListProp
downloadIcon,
progress,
appendAction,
appendActionVisible,
itemRender,
},
ref,
@ -225,12 +226,14 @@ const InternalUploadList: React.ForwardRefRenderFunction<unknown, UploadListProp
{/* Append action */}
{appendAction && (
<CSSMotion {...motionConfig}>
<CSSMotion {...motionConfig} visible={appendActionVisible} forceRender>
{({ className: motionClassName, style: motionStyle }) =>
cloneElement(appendAction, oriProps => ({
className: classNames(oriProps.className, motionClassName),
style: {
...motionStyle,
// prevent the element has hover css pseudo-class that may cause animation to end prematurely.
pointerEvents: motionClassName ? 'none' : undefined,
...oriProps.style,
},
}))
@ -254,6 +257,7 @@ UploadList.defaultProps = {
showRemoveIcon: true,
showDownloadIcon: false,
showPreviewIcon: true,
appendActionVisible: true,
previewFile: previewImage,
isImageUrl,
};

View File

@ -867,4 +867,26 @@ describe('Upload', () => {
expect(onChange.mock.calls[0][0].fileList).toHaveLength(1);
});
// https://github.com/ant-design/ant-design/issues/33819
it('should show the animation of the upload children leaving when the upload children becomes null', async () => {
const wrapper = mount(
<Upload listType="picture-card">
<button type="button">upload</button>
</Upload>,
);
wrapper.setProps({ children: null });
expect(wrapper.find('.ant-upload-select-picture-card').getDOMNode().style.display).not.toBe(
'none',
);
await act(async () => {
await sleep(100);
wrapper
.find('.ant-upload-select-picture-card')
.getDOMNode()
.dispatchEvent(new Event('animationend'));
await sleep(20);
});
expect(wrapper.find('.ant-upload-select-picture-card').getDOMNode().style.display).toBe('none');
});
});

View File

@ -155,5 +155,6 @@ export interface UploadListProps<T = any> {
iconRender?: (file: UploadFile<T>, listType?: UploadListType) => React.ReactNode;
isImageUrl?: (file: UploadFile) => boolean;
appendAction?: React.ReactNode;
appendActionVisible?: boolean;
itemRender?: ItemRender<T>;
}