fix(modal): Modal.xxx onCancel close argument is not a function (#36600)

* fix(modal):  fix the error of `onCancel` parameter of modal returned by `useModal`

resolve: 36581

ref: https://github.com/ant-design/ant-design/issues/36581#issuecomment-1189396007

* test(confirm): 补充 modal 测试用例

* test(modal): 添加测试用例

* Revert "fix(modal):  fix the error of `onCancel` parameter of modal returned by `useModal`"

This reverts commit e4fcb3e62add63193ec9081b2c21e657379bebfb.

* fix(modal): fix modal onOk/onCancel method is not a valid function when there is a close parameter

closed: #36581

* chore: cancel the introduction of `noop` from third-party library
This commit is contained in:
Wuxh 2022-07-23 16:04:23 +08:00 committed by GitHub
parent 09de419253
commit 16ecdbb58d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 259 additions and 6 deletions

View File

@ -305,7 +305,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
});
describe('should not close modals when click confirm button when onOk has argument', () => {
['info', 'success', 'warning', 'error'].forEach(type => {
['confirm', 'info', 'success', 'warning', 'error'].forEach(type => {
it(type, async () => {
jest.useFakeTimers();
Modal[type]({
@ -318,7 +318,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
await sleep();
});
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
$$('.ant-btn')[0].click();
$$('.ant-btn-primary')[0].click();
await act(async () => {
jest.runAllTimers();
@ -674,4 +674,149 @@ describe('Modal.confirm triggers callbacks correctly', () => {
const { width } = $$('.ant-modal-body')[0].style;
expect(width).toBe('500px');
});
describe('the callback close should be a method when onCancel has a close parameter', () => {
['confirm', 'info', 'success', 'warning', 'error'].forEach(type => {
it(`click the close icon to trigger ${type} onCancel`, async () => {
jest.useFakeTimers();
const mock = jest.fn();
Modal[type]({
closable: true,
onCancel: close => mock(close),
});
await act(async () => {
jest.runAllTimers();
await sleep();
});
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
$$('.ant-modal-close')[0].click();
await act(async () => {
jest.runAllTimers();
await sleep();
});
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
expect(mock).toBeCalledWith(expect.any(Function));
jest.useRealTimers();
});
});
['confirm', 'info', 'success', 'warning', 'error'].forEach(type => {
it(`press ESC to trigger ${type} onCancel`, async () => {
jest.useFakeTimers();
const mock = jest.fn();
Modal[type]({
keyboard: true,
onCancel: close => mock(close),
});
jest.runAllTimers();
await sleep();
jest.runAllTimers();
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
TestUtils.Simulate.keyDown($$('.ant-modal')[0], {
keyCode: KeyCode.ESC,
});
jest.runAllTimers();
await sleep(0);
jest.runAllTimers();
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
expect(mock).toBeCalledWith(expect.any(Function));
jest.useRealTimers();
});
});
['confirm', 'info', 'success', 'warning', 'error'].forEach(type => {
it(`click the mask to trigger ${type} onCancel`, async () => {
jest.useFakeTimers();
const mock = jest.fn();
Modal[type]({
maskClosable: true,
onCancel: close => mock(close),
});
await act(async () => {
jest.runAllTimers();
await sleep();
});
expect($$('.ant-modal-mask')).toHaveLength(1);
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
$$('.ant-modal-wrap')[0].click();
await act(async () => {
jest.runAllTimers();
await sleep();
});
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
expect(mock).toBeCalledWith(expect.any(Function));
jest.useRealTimers();
});
});
});
it('confirm modal click Cancel button close callback is a function', async () => {
jest.useFakeTimers();
const mock = jest.fn();
Modal.confirm({
onCancel: close => mock(close),
});
await act(async () => {
jest.runAllTimers();
await sleep();
});
$$('.ant-modal-confirm-btns > .ant-btn')[0].click();
await act(async () => {
jest.runAllTimers();
await sleep();
});
expect(mock).toBeCalledWith(expect.any(Function));
jest.useRealTimers();
});
it('close can close modal when onCancel has a close parameter', async () => {
jest.useFakeTimers();
Modal.confirm({
onCancel: close => close(),
});
await act(async () => {
jest.runAllTimers();
await sleep();
});
expect($$('.ant-modal-confirm-confirm')).toHaveLength(1);
$$('.ant-modal-confirm-btns > .ant-btn')[0].click();
await act(async () => {
jest.runAllTimers();
await sleep();
});
expect($$('.ant-modal-confirm-confirm')).toHaveLength(0);
jest.useRealTimers();
});
});

View File

@ -1,9 +1,11 @@
import CSSMotion from 'rc-motion';
import { genCSSMotion } from 'rc-motion/lib/CSSMotion';
import KeyCode from 'rc-util/lib/KeyCode';
import React from 'react';
import { act } from 'react-dom/test-utils';
import TestUtils, { act } from 'react-dom/test-utils';
import Modal from '..';
import { fireEvent, render } from '../../../tests/utils';
import { fireEvent, render, sleep } from '../../../tests/utils';
import Button from '../../button';
import ConfigProvider from '../../config-provider';
import Input from '../../input';
@ -193,4 +195,110 @@ describe('Modal.hook', () => {
fireEvent.click(container.querySelectorAll('.open-hook-modal-btn')[0]);
expect(document.body.classList.contains('ant-modal-confirm-title')).toBeFalsy();
});
it('the callback close should be a method when onCancel has a close parameter', async () => {
jest.useFakeTimers();
const clear = async function clear() {
await act(async () => {
jest.runAllTimers();
await sleep();
});
};
const mockFn = jest.fn();
const Demo = () => {
const [modal, contextHolder] = Modal.useModal();
const openBrokenModal = React.useCallback(() => {
modal.confirm({
closable: true,
keyboard: true,
maskClosable: true,
onCancel: close => mockFn(close),
});
}, [modal]);
return (
<div className="App">
{contextHolder}
<div className="open-hook-modal-btn" onClick={openBrokenModal}>
Test hook modal
</div>
</div>
);
};
const { container } = render(<Demo />);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(0);
// First open
fireEvent.click(container.querySelectorAll('.open-hook-modal-btn')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(1);
// Click mask to close
fireEvent.click(document.body.querySelectorAll('.ant-modal-wrap')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(0);
// Second open
fireEvent.click(container.querySelectorAll('.open-hook-modal-btn')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(1);
// Press ESC to turn off
TestUtils.Simulate.keyDown(document.body.querySelectorAll('.ant-modal')[0], {
keyCode: KeyCode.ESC,
});
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(0);
// Third open
fireEvent.click(container.querySelectorAll('.open-hook-modal-btn')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(1);
// Click the close icon to close
fireEvent.click(document.body.querySelectorAll('.ant-modal-close')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(0);
// Last open
fireEvent.click(container.querySelectorAll('.open-hook-modal-btn')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(1);
// Click the Cancel button to close (invalid)
fireEvent.click(document.body.querySelectorAll('.ant-modal-confirm-btns > .ant-btn')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(1);
mockFn.mockImplementation(close => close());
// Click the Cancel button to close (valid)
fireEvent.click(document.body.querySelectorAll('.ant-modal-confirm-btns > .ant-btn')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(0);
// Close called 5 times
expect(mockFn).toHaveBeenCalledTimes(5);
expect(mockFn.mock.calls).toEqual(Array.from({ length: 5 }, () => [expect.any(Function)]));
jest.useRealTimers();
});
});

View File

@ -34,7 +34,7 @@ export default function confirm(config: ModalFuncProps) {
function destroy(...args: any[]) {
const triggerCancel = args.some(param => param && param.triggerCancel);
if (config.onCancel && triggerCancel) {
config.onCancel(...args);
config.onCancel(() => {}, ...args.slice(1));
}
for (let i = 0; i < destroyFns.length; i++) {
const fn = destroyFns[i];

View File

@ -36,7 +36,7 @@ const HookModal: React.ForwardRefRenderFunction<HookModalRef, HookModalProps> =
setVisible(false);
const triggerCancel = args.some(param => param && param.triggerCancel);
if (innerConfig.onCancel && triggerCancel) {
innerConfig.onCancel();
innerConfig.onCancel(() => {}, ...args.slice(1));
}
};