mirror of
https://github.com/ant-design/ant-design.git
synced 2026-02-09 02:49:18 +08:00
chore: sync feature into next
This commit is contained in:
4
.github/workflows/preview-deploy.yml
vendored
4
.github/workflows/preview-deploy.yml
vendored
@@ -61,7 +61,7 @@ jobs:
|
||||
steps:
|
||||
# We need get PR id first
|
||||
- name: download pr artifact
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
uses: dawidd6/action-download-artifact@v10
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
# Download site artifact
|
||||
- name: download site artifact
|
||||
if: ${{ fromJSON(needs.upstream-workflow-summary.outputs.build-success) }}
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
uses: dawidd6/action-download-artifact@v10
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
2
.github/workflows/upgrade-deps.yml
vendored
2
.github/workflows/upgrade-deps.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }} # Cannot be default!!!
|
||||
assignees: 'afc163, yoyo837, Wxh16144'
|
||||
assignees: 'afc163, yoyo837, Wxh16144, li-jia-nan, thinkasany'
|
||||
title: "chore: upgrade deps"
|
||||
commit-message: "chore: upgrade deps"
|
||||
body: |
|
||||
|
||||
1
.github/workflows/verify-files-modify.yml
vendored
1
.github/workflows/verify-files-modify.yml
vendored
@@ -19,6 +19,7 @@ jobs:
|
||||
with:
|
||||
forbid-paths: '.github/, scripts/'
|
||||
forbid-files: 'CHANGELOG.zh-CN.md, CHANGELOG.en-US.md, LICENSE'
|
||||
skip-contribution-count: 10
|
||||
skip-verify-authority: write
|
||||
skip-label: skip-verify-files
|
||||
assignees: 'afc163, zombieJ, xrkffgg, MadCcc'
|
||||
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
|
||||
# We need get persist-index first
|
||||
- name: download image snapshot artifact
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
uses: dawidd6/action-download-artifact@v10
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
- name: download report artifact
|
||||
id: download_report
|
||||
if: ${{ needs.upstream-workflow-summary.outputs.build-status == 'success' || needs.upstream-workflow-summary.outputs.build-status == 'failure' }}
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
uses: dawidd6/action-download-artifact@v10
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
# We need get persist key first
|
||||
- name: Download Visual Regression Ref
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
uses: dawidd6/action-download-artifact@v10
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
|
||||
- name: Download Visual-Regression Artifact
|
||||
if: ${{ fromJSON(needs.upstream-workflow-summary.outputs.build-success) }}
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
uses: dawidd6/action-download-artifact@v10
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
@@ -3,20 +3,31 @@ import { Provider as MotionProvider } from '@rc-component/motion';
|
||||
|
||||
import { useToken } from '../theme/internal';
|
||||
|
||||
const MotionCacheContext = React.createContext(true);
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
MotionCacheContext.displayName = 'MotionCacheContext';
|
||||
}
|
||||
|
||||
export interface MotionWrapperProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function MotionWrapper(props: MotionWrapperProps): React.ReactElement {
|
||||
const parentMotion = React.useContext(MotionCacheContext);
|
||||
|
||||
const { children } = props;
|
||||
const [, token] = useToken();
|
||||
const { motion } = token;
|
||||
|
||||
const needWrapMotionProviderRef = React.useRef(false);
|
||||
needWrapMotionProviderRef.current = needWrapMotionProviderRef.current || motion === false;
|
||||
needWrapMotionProviderRef.current ||= parentMotion !== motion;
|
||||
|
||||
if (needWrapMotionProviderRef.current) {
|
||||
return <MotionProvider motion={motion}>{children}</MotionProvider>;
|
||||
return (
|
||||
<MotionCacheContext.Provider value={motion}>
|
||||
<MotionProvider motion={motion}>{children}</MotionProvider>
|
||||
</MotionCacheContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
return children as React.ReactElement;
|
||||
|
||||
@@ -7,10 +7,10 @@ import { resetWarned } from '../../_util/warning';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import { fireEvent, render } from '../../../tests/utils';
|
||||
import Button from '../../button';
|
||||
import Form from '../../form';
|
||||
import Input from '../../input';
|
||||
import Select from '../../select';
|
||||
import Table from '../../table';
|
||||
import Form from '../../form';
|
||||
|
||||
describe('ConfigProvider', () => {
|
||||
mountTest(() => (
|
||||
@@ -185,4 +185,43 @@ describe('ConfigProvider', () => {
|
||||
expect(container.querySelector('#variant-input-5')).toHaveClass('ant-input-filled');
|
||||
expect(container.querySelector('#variant-input-6')).toHaveClass('ant-input-outlined');
|
||||
});
|
||||
|
||||
it('motion config should not trigger re-mount', () => {
|
||||
let mountTime = 0;
|
||||
|
||||
const Render = () => {
|
||||
React.useEffect(() => {
|
||||
mountTime += 1;
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// No motion
|
||||
const { rerender } = render(
|
||||
<ConfigProvider theme={{ token: { motion: false } }}>
|
||||
<Render />
|
||||
</ConfigProvider>,
|
||||
{
|
||||
wrapper: undefined!,
|
||||
},
|
||||
);
|
||||
expect(mountTime).toBe(1);
|
||||
|
||||
// Motion
|
||||
rerender(
|
||||
<ConfigProvider theme={{ token: { motion: true } }}>
|
||||
<Render />
|
||||
</ConfigProvider>,
|
||||
);
|
||||
expect(mountTime).toBe(1);
|
||||
|
||||
// No motion again
|
||||
rerender(
|
||||
<ConfigProvider theme={{ token: { motion: false } }}>
|
||||
<Render />
|
||||
</ConfigProvider>,
|
||||
);
|
||||
expect(mountTime).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -280,7 +280,7 @@ function getGlobalIconPrefixCls() {
|
||||
return globalIconPrefixCls || defaultIconPrefixCls;
|
||||
}
|
||||
|
||||
interface GlobalConfigProps {
|
||||
export interface GlobalConfigProps {
|
||||
prefixCls?: string;
|
||||
iconPrefixCls?: string;
|
||||
theme?: ThemeConfig;
|
||||
|
||||
@@ -376,7 +376,7 @@ exports[`renders components/image/demo/placeholder.tsx extend context correctly
|
||||
>
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?undefined"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?1479772800000"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -365,7 +365,7 @@ exports[`renders components/image/demo/placeholder.tsx correctly 1`] = `
|
||||
>
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?undefined"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?1479772800000"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
||||
import { Button, Image, Space } from 'antd';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [random, setRandom] = useState<number>();
|
||||
const [random, setRandom] = useState<number>(Date.now());
|
||||
|
||||
return (
|
||||
<Space size={12}>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { SmileOutlined } from '@ant-design/icons';
|
||||
import RcCSSMotion from '@rc-component/motion';
|
||||
import { genCSSMotion as genRcCSSMotion } from '@rc-component/motion/lib/CSSMotion';
|
||||
import KeyCode from '@rc-component/util/lib/KeyCode';
|
||||
import { resetWarned } from '@rc-component/util/lib/warning';
|
||||
|
||||
@@ -9,7 +7,7 @@ import type { ModalFuncProps } from '..';
|
||||
import Modal from '..';
|
||||
import { act, fireEvent, render, waitFakeTimer } from '../../../tests/utils';
|
||||
import App from '../../app';
|
||||
import ConfigProvider, { defaultPrefixCls } from '../../config-provider';
|
||||
import ConfigProvider, { defaultPrefixCls, GlobalConfigProps } from '../../config-provider';
|
||||
import type { ModalFunc } from '../confirm';
|
||||
import destroyFns from '../destroyFns';
|
||||
|
||||
@@ -17,8 +15,6 @@ import destroyFns from '../destroyFns';
|
||||
|
||||
const { confirm } = Modal;
|
||||
|
||||
jest.mock('@rc-component/motion');
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
@@ -71,12 +67,10 @@ jest.mock('../../_util/ActionButton', () => {
|
||||
});
|
||||
|
||||
describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
// Inject `@rc-component/motion` to replace with No transition support
|
||||
const MockRcCSSMotion = genRcCSSMotion(false);
|
||||
Object.keys(MockRcCSSMotion).forEach((key) => {
|
||||
// @ts-ignore
|
||||
RcCSSMotion[key] = MockRcCSSMotion[key];
|
||||
});
|
||||
const configWarp = (conf?: GlobalConfigProps) => {
|
||||
ConfigProvider.config({ ...conf, theme: { token: { motion: false } } });
|
||||
};
|
||||
configWarp();
|
||||
|
||||
// // Mock for @rc-component/util raf
|
||||
// window.requestAnimationFrame = callback => {
|
||||
@@ -103,6 +97,9 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
if (errorStr.includes('was not wrapped in act(...)')) {
|
||||
return;
|
||||
}
|
||||
if (errorStr.includes('Static function can not')) {
|
||||
return;
|
||||
}
|
||||
|
||||
originError(...args);
|
||||
};
|
||||
@@ -145,6 +142,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
confirm({
|
||||
content: 'some descriptions',
|
||||
});
|
||||
|
||||
await waitFakeTimer();
|
||||
expect(document.querySelector('.ant-modal-confirm-title')).toBe(null);
|
||||
});
|
||||
@@ -516,7 +514,6 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
});
|
||||
|
||||
it('should warning when pass a string as icon props', async () => {
|
||||
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
confirm({
|
||||
content: 'some descriptions',
|
||||
icon: 'ab',
|
||||
@@ -524,7 +521,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
|
||||
await waitFakeTimer();
|
||||
|
||||
expect(warnSpy).not.toHaveBeenCalled();
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
confirm({
|
||||
content: 'some descriptions',
|
||||
icon: 'question',
|
||||
@@ -532,10 +529,9 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
|
||||
await waitFakeTimer();
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
`Warning: [antd: Modal] \`icon\` is using ReactNode instead of string naming in v4. Please check \`question\` at https://ant.design/components/icon`,
|
||||
);
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('icon can be null to hide icon', async () => {
|
||||
@@ -584,7 +580,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
});
|
||||
|
||||
it('should be able to global config rootPrefixCls', async () => {
|
||||
ConfigProvider.config({ prefixCls: 'my', iconPrefixCls: 'bamboo' });
|
||||
configWarp({ prefixCls: 'my', iconPrefixCls: 'bamboo' });
|
||||
confirm({ title: 'title', icon: <SmileOutlined /> });
|
||||
|
||||
await waitFakeTimer();
|
||||
@@ -593,7 +589,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
expect(document.querySelectorAll('.my-btn').length).toBe(2);
|
||||
expect(document.querySelectorAll('.bamboo-smile').length).toBe(1);
|
||||
expect(document.querySelectorAll('.my-modal-confirm').length).toBe(1);
|
||||
ConfigProvider.config({ prefixCls: defaultPrefixCls, iconPrefixCls: undefined });
|
||||
configWarp({ prefixCls: defaultPrefixCls, iconPrefixCls: undefined });
|
||||
});
|
||||
|
||||
it('should be able to config rootPrefixCls', async () => {
|
||||
@@ -885,8 +881,8 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
expect(document.querySelector('.custom-footer-ele')).toBeTruthy();
|
||||
});
|
||||
it('should be able to config holderRender', async () => {
|
||||
ConfigProvider.config({
|
||||
holderRender: (children) => (
|
||||
configWarp({
|
||||
holderRender: (children: React.ReactNode) => (
|
||||
<ConfigProvider prefixCls="test" iconPrefixCls="icon">
|
||||
{children}
|
||||
</ConfigProvider>
|
||||
@@ -898,12 +894,14 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
expect(document.querySelectorAll('.anticon-exclamation-circle')).toHaveLength(0);
|
||||
expect(document.querySelectorAll('.test-modal-root')).toHaveLength(1);
|
||||
expect(document.querySelectorAll('.icon-exclamation-circle')).toHaveLength(1);
|
||||
ConfigProvider.config({ holderRender: undefined });
|
||||
configWarp({ holderRender: undefined });
|
||||
});
|
||||
it('should be able to config holderRender config rtl', async () => {
|
||||
document.body.innerHTML = '';
|
||||
ConfigProvider.config({
|
||||
holderRender: (children) => <ConfigProvider direction="rtl">{children}</ConfigProvider>,
|
||||
configWarp({
|
||||
holderRender: (children: React.ReactNode) => (
|
||||
<ConfigProvider direction="rtl">{children}</ConfigProvider>
|
||||
),
|
||||
});
|
||||
Modal.confirm({ content: 'hai' });
|
||||
await waitFakeTimer();
|
||||
@@ -918,18 +916,18 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
Modal.confirm({ content: 'hai', direction: 'ltr' });
|
||||
await waitFakeTimer();
|
||||
expect(document.querySelector('.ant-modal-confirm-rtl')).toBeFalsy();
|
||||
ConfigProvider.config({ holderRender: undefined });
|
||||
configWarp({ holderRender: undefined });
|
||||
});
|
||||
it('should be able to config holderRender and static config', async () => {
|
||||
// level 1
|
||||
ConfigProvider.config({ prefixCls: 'prefix-1' });
|
||||
configWarp({ prefixCls: 'prefix-1' });
|
||||
Modal.confirm({ content: 'hai' });
|
||||
await waitFakeTimer();
|
||||
expect(document.querySelectorAll('.prefix-1-modal-root')).toHaveLength(1);
|
||||
expect($$('.prefix-1-btn')).toHaveLength(2);
|
||||
// level 2
|
||||
document.body.innerHTML = '';
|
||||
ConfigProvider.config({
|
||||
configWarp({
|
||||
prefixCls: 'prefix-1',
|
||||
holderRender: (children) => <ConfigProvider prefixCls="prefix-2">{children}</ConfigProvider>,
|
||||
});
|
||||
@@ -946,11 +944,11 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
expect(document.querySelectorAll('.prefix-3-btn')).toHaveLength(2);
|
||||
// clear
|
||||
Modal.config({ rootPrefixCls: '' });
|
||||
ConfigProvider.config({ prefixCls: '', holderRender: undefined });
|
||||
configWarp({ prefixCls: '', holderRender: undefined });
|
||||
});
|
||||
it('should be able to config holderRender antd locale', async () => {
|
||||
document.body.innerHTML = '';
|
||||
ConfigProvider.config({
|
||||
configWarp({
|
||||
holderRender: (children) => (
|
||||
<ConfigProvider locale={{ Modal: { okText: 'test' } } as any}>{children}</ConfigProvider>
|
||||
),
|
||||
@@ -958,7 +956,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
Modal.confirm({ content: 'hai' });
|
||||
await waitFakeTimer();
|
||||
expect(document.querySelector('.ant-btn-primary')?.textContent).toBe('test');
|
||||
ConfigProvider.config({ holderRender: undefined });
|
||||
configWarp({ holderRender: undefined });
|
||||
});
|
||||
|
||||
it('onCancel and onOk return any results and should be closed', async () => {
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import React from 'react';
|
||||
import RcCSSMotion from '@rc-component/motion';
|
||||
import { genCSSMotion as genRcCSSMotion } from '@rc-component/motion/lib/CSSMotion';
|
||||
import KeyCode from '@rc-component/util/lib/KeyCode';
|
||||
|
||||
import Modal from '..';
|
||||
import { act, fireEvent, render, waitFakeTimer } from '../../../tests/utils';
|
||||
import Button from '../../button';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
import ConfigProvider, { ConfigProviderProps } from '../../config-provider';
|
||||
import Input from '../../input';
|
||||
import zhCN from '../../locale/zh_CN';
|
||||
import type { ModalFunc } from '../confirm';
|
||||
|
||||
jest.mock('@rc-component/util/lib/Portal');
|
||||
jest.mock('@rc-component/motion');
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
@@ -27,12 +24,9 @@ jest.mock('react-dom', () => {
|
||||
});
|
||||
|
||||
describe('Modal.hook', () => {
|
||||
// Inject `@rc-component/motion` to replace with No transition support
|
||||
const MockRcCSSMotion = genRcCSSMotion(false);
|
||||
Object.keys(MockRcCSSMotion).forEach((key) => {
|
||||
// @ts-ignore
|
||||
RcCSSMotion[key] = MockRcCSSMotion[key];
|
||||
});
|
||||
const ConfigWarp = (conf?: ConfigProviderProps) => {
|
||||
return <ConfigProvider {...conf} theme={{ token: { motion: false } }} />;
|
||||
};
|
||||
|
||||
it('hooks support context', () => {
|
||||
jest.useFakeTimers();
|
||||
@@ -60,7 +54,11 @@ describe('Modal.hook', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const { container } = render(<Demo />);
|
||||
const { container } = render(
|
||||
<ConfigWarp>
|
||||
<Demo />
|
||||
</ConfigWarp>,
|
||||
);
|
||||
fireEvent.click(container.querySelectorAll('button')[0]);
|
||||
|
||||
expect(document.body.querySelectorAll('.test-hook')[0].textContent).toBe('bamboo');
|
||||
@@ -105,12 +103,12 @@ describe('Modal.hook', () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<ConfigWarp>
|
||||
{contextHolder}
|
||||
<div className="open-hook-modal-btn" onClick={showConfirm}>
|
||||
confirm
|
||||
</div>
|
||||
</div>
|
||||
</ConfigWarp>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -145,9 +143,9 @@ describe('Modal.hook', () => {
|
||||
};
|
||||
|
||||
const { container } = render(
|
||||
<ConfigProvider direction="rtl">
|
||||
<ConfigWarp direction="rtl">
|
||||
<Demo />
|
||||
</ConfigProvider>,
|
||||
</ConfigWarp>,
|
||||
);
|
||||
|
||||
fireEvent.click(container.querySelectorAll('button')[0]);
|
||||
@@ -172,12 +170,12 @@ describe('Modal.hook', () => {
|
||||
}, [modal]);
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<ConfigWarp>
|
||||
{contextHolder}
|
||||
<div className="open-hook-modal-btn" onClick={openBrokenModal}>
|
||||
Test hook modal
|
||||
</div>
|
||||
</div>
|
||||
</ConfigWarp>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -196,7 +194,7 @@ describe('Modal.hook', () => {
|
||||
const Demo = () => {
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
|
||||
const openBrokenModal = React.useCallback(() => {
|
||||
const openBrokenModal = () => {
|
||||
const instance = modal.info({
|
||||
title: 'Light',
|
||||
});
|
||||
@@ -204,7 +202,45 @@ describe('Modal.hook', () => {
|
||||
instance.update({
|
||||
title: 'Bamboo',
|
||||
});
|
||||
}, [modal]);
|
||||
|
||||
instance.update((ori) => ({
|
||||
...ori,
|
||||
content: 'Little',
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<ConfigWarp>
|
||||
{contextHolder}
|
||||
<div className="open-hook-modal-btn" onClick={openBrokenModal}>
|
||||
Test hook modal
|
||||
</div>
|
||||
</ConfigWarp>
|
||||
);
|
||||
};
|
||||
|
||||
const { container } = render(<Demo />);
|
||||
fireEvent.click(container.querySelectorAll('.open-hook-modal-btn')[0]);
|
||||
expect(document.body.querySelector('.ant-modal-confirm-title')!.textContent).toEqual('Bamboo');
|
||||
expect(document.body.querySelector('.ant-modal-confirm-content')!.textContent).toEqual(
|
||||
'Little',
|
||||
);
|
||||
});
|
||||
|
||||
it('support update config', () => {
|
||||
const Demo = () => {
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
|
||||
const openBrokenModal = () => {
|
||||
const instance = modal.info({
|
||||
title: 'Light',
|
||||
content: 'Little',
|
||||
});
|
||||
|
||||
instance.update(() => ({
|
||||
title: 'Bamboo',
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
@@ -217,9 +253,10 @@ describe('Modal.hook', () => {
|
||||
};
|
||||
|
||||
const { container } = render(<Demo />);
|
||||
fireEvent.click(container.querySelectorAll('.open-hook-modal-btn')[0]);
|
||||
expect(document.body.querySelectorAll('.ant-modal-confirm-title')[0].textContent).toEqual(
|
||||
'Bamboo',
|
||||
fireEvent.click(container.querySelector('.open-hook-modal-btn')!);
|
||||
expect(document.body.querySelector('.ant-modal-confirm-title')!.textContent).toEqual('Bamboo');
|
||||
expect(document.body.querySelector('.ant-modal-confirm-content')!.textContent).toEqual(
|
||||
'Little',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -268,12 +305,12 @@ describe('Modal.hook', () => {
|
||||
}, [modal]);
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<ConfigWarp>
|
||||
{contextHolder}
|
||||
<div className="open-hook-modal-btn" onClick={openBrokenModal}>
|
||||
Test hook modal
|
||||
</div>
|
||||
</div>
|
||||
</ConfigWarp>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -360,7 +397,7 @@ describe('Modal.hook', () => {
|
||||
});
|
||||
}, []);
|
||||
|
||||
return <ConfigProvider autoInsertSpaceInButton={false}>{contextHolder}</ConfigProvider>;
|
||||
return <ConfigWarp autoInsertSpaceInButton={false}>{contextHolder}</ConfigWarp>;
|
||||
};
|
||||
|
||||
render(<Demo />);
|
||||
@@ -375,7 +412,7 @@ describe('Modal.hook', () => {
|
||||
React.useEffect(() => {
|
||||
modal.confirm({ title: 'Confirm', afterClose });
|
||||
}, []);
|
||||
return contextHolder;
|
||||
return <ConfigWarp>{contextHolder}</ConfigWarp>;
|
||||
};
|
||||
|
||||
render(<Demo />);
|
||||
@@ -388,37 +425,23 @@ describe('Modal.hook', () => {
|
||||
it('should be applied correctly locale', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const Demo: React.FC<{ count: number }> = ({ count }) => {
|
||||
const Demo: React.FC<{ zh?: boolean }> = ({ zh }) => {
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
|
||||
React.useEffect(() => {
|
||||
const instance = Modal.confirm({});
|
||||
return () => {
|
||||
instance.destroy();
|
||||
};
|
||||
}, [count]);
|
||||
const instance = modal.confirm({});
|
||||
return () => instance.destroy();
|
||||
}, []);
|
||||
|
||||
let node = null;
|
||||
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
node = <ConfigProvider locale={zhCN}>{node}</ConfigProvider>;
|
||||
}
|
||||
|
||||
return node;
|
||||
return <ConfigWarp locale={zh ? zhCN : undefined}>{contextHolder}</ConfigWarp>;
|
||||
};
|
||||
|
||||
const { rerender } = render(<div />);
|
||||
const { unmount } = render(<Demo zh />);
|
||||
await waitFakeTimer();
|
||||
expect(document.body.querySelector('.ant-btn-primary')!.textContent).toEqual('确 定');
|
||||
unmount();
|
||||
|
||||
for (let i = 10; i > 0; i -= 1) {
|
||||
rerender(<Demo count={i} />);
|
||||
|
||||
await waitFakeTimer();
|
||||
|
||||
expect(document.body.querySelector('.ant-btn-primary')!.textContent).toEqual('确 定');
|
||||
fireEvent.click(document.body.querySelector('.ant-btn-primary')!);
|
||||
|
||||
await waitFakeTimer();
|
||||
}
|
||||
|
||||
rerender(<Demo count={0} />);
|
||||
render(<Demo />);
|
||||
await waitFakeTimer();
|
||||
expect(document.body.querySelector('.ant-btn-primary')!.textContent).toEqual('OK');
|
||||
|
||||
@@ -449,7 +472,7 @@ describe('Modal.hook', () => {
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return contextHolder;
|
||||
return <ConfigWarp>{contextHolder}</ConfigWarp>;
|
||||
};
|
||||
|
||||
render(<Demo />);
|
||||
@@ -487,7 +510,7 @@ describe('Modal.hook', () => {
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return contextHolder;
|
||||
return <ConfigWarp>{contextHolder}</ConfigWarp>;
|
||||
};
|
||||
|
||||
render(<Demo />);
|
||||
|
||||
@@ -15,7 +15,7 @@ function getRootPrefixCls() {
|
||||
return defaultRootPrefixCls;
|
||||
}
|
||||
|
||||
type ConfigUpdate = ModalFuncProps | ((prevConfig: ModalFuncProps) => ModalFuncProps);
|
||||
export type ConfigUpdate = ModalFuncProps | ((prevConfig: ModalFuncProps) => ModalFuncProps);
|
||||
|
||||
export type ModalFunc = (props: ModalFuncProps) => {
|
||||
destroy: () => void;
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as React from 'react';
|
||||
import { ConfigContext } from '../../config-provider';
|
||||
import defaultLocale from '../../locale/en_US';
|
||||
import useLocale from '../../locale/useLocale';
|
||||
import type { ConfigUpdate } from '../confirm';
|
||||
import ConfirmDialog from '../ConfirmDialog';
|
||||
import type { ModalFuncProps } from '../interface';
|
||||
|
||||
@@ -18,7 +19,7 @@ export interface HookModalProps {
|
||||
|
||||
export interface HookModalRef {
|
||||
destroy: () => void;
|
||||
update: (config: ModalFuncProps) => void;
|
||||
update: (config: ConfigUpdate) => void;
|
||||
}
|
||||
|
||||
const HookModal: React.ForwardRefRenderFunction<HookModalRef, HookModalProps> = (
|
||||
@@ -47,11 +48,15 @@ const HookModal: React.ForwardRefRenderFunction<HookModalRef, HookModalProps> =
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
destroy: close,
|
||||
update: (newConfig: ModalFuncProps) => {
|
||||
setInnerConfig((originConfig) => ({
|
||||
...originConfig,
|
||||
...newConfig,
|
||||
}));
|
||||
update: (newConfig) => {
|
||||
setInnerConfig((originConfig) => {
|
||||
const nextConfig = typeof newConfig === 'function' ? newConfig(originConfig) : newConfig;
|
||||
|
||||
return {
|
||||
...originConfig,
|
||||
...nextConfig,
|
||||
};
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -39,11 +39,12 @@ function useModal(): readonly [instance: HookAPI, contextHolder: React.ReactElem
|
||||
const holderRef = React.useRef<ElementsHolderRef>(null);
|
||||
|
||||
// ========================== Effect ==========================
|
||||
const [actionQueue, setActionQueue] = React.useState<(() => void)[]>([]);
|
||||
const [actionQueue, setActionQueue] = React.useState<VoidFunction[]>([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (actionQueue.length) {
|
||||
const cloneQueue = [...actionQueue];
|
||||
|
||||
cloneQueue.forEach((action) => {
|
||||
action();
|
||||
});
|
||||
@@ -103,7 +104,7 @@ function useModal(): readonly [instance: HookAPI, contextHolder: React.ReactElem
|
||||
},
|
||||
update: (newConfig) => {
|
||||
function updateAction() {
|
||||
modalRef.current?.update(newConfig as ModalFuncProps);
|
||||
modalRef.current?.update(newConfig);
|
||||
}
|
||||
|
||||
if (modalRef.current) {
|
||||
|
||||
@@ -263,11 +263,11 @@ const ListItem = React.forwardRef<HTMLDivElement, ListItemProps>(
|
||||
const loadingProgress =
|
||||
'percent' in file ? (
|
||||
<Progress
|
||||
{...progressProps}
|
||||
type="line"
|
||||
percent={file.percent}
|
||||
aria-label={file['aria-label']}
|
||||
aria-labelledby={file['aria-labelledby']}
|
||||
{...progressProps}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"watch": {
|
||||
"_nodeModulesRegexes": ["rc-.*"]
|
||||
},
|
||||
"hmr": false,
|
||||
"devtool": false,
|
||||
"experimental": {
|
||||
"magicComment": true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "antd",
|
||||
"version": "5.25.4",
|
||||
"version": "5.26.0-alpha.0",
|
||||
"description": "An enterprise-class UI design language and React components implementation",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -284,6 +284,7 @@
|
||||
"package-manager-detector": "^1.0.0",
|
||||
"pixelmatch": "^7.1.0",
|
||||
"pngjs": "^7.0.0",
|
||||
"portfinder": "^1.0.37",
|
||||
"prettier": "^3.4.1",
|
||||
"pretty-format": "^29.7.0",
|
||||
"prismjs": "^1.29.0",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { globSync } from 'glob';
|
||||
import { createServer } from 'http-server';
|
||||
import fetch from 'isomorphic-fetch';
|
||||
import uniq from 'lodash/uniq';
|
||||
import portfinder from 'portfinder';
|
||||
|
||||
const components = uniq(
|
||||
globSync('components/!(overview)/*.md', { cwd: join(process.cwd()), dot: false }).map((path) =>
|
||||
@@ -15,8 +16,11 @@ const components = uniq(
|
||||
|
||||
describe('site test', () => {
|
||||
let server: http.Server | https.Server;
|
||||
const port = 3000;
|
||||
const portPromise = portfinder.getPortPromise({
|
||||
port: 3000,
|
||||
});
|
||||
const render = async (path: string) => {
|
||||
const port = await portPromise;
|
||||
const resp = await fetch(`http://127.0.0.1:${port}${path}`).then(async (res) => {
|
||||
const html: string = await res.text();
|
||||
const $ = load(html, { xml: true });
|
||||
@@ -46,7 +50,8 @@ describe('site test', () => {
|
||||
expect(tables.length).toMatchSnapshot();
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
const port = await portPromise;
|
||||
server = createServer({ root: join(process.cwd(), '_site') });
|
||||
server.listen(port);
|
||||
|
||||
|
||||
@@ -18,5 +18,5 @@
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["./"],
|
||||
"exclude": ["**/node_modules"]
|
||||
"exclude": ["**/node_modules", "**/*.js"]
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export const sleep = async (timeout = 0) => {
|
||||
});
|
||||
};
|
||||
|
||||
const customRender = (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>) =>
|
||||
const customRender = (ui: ReactElement, options?: Partial<RenderOptions>) =>
|
||||
render(ui, { wrapper: StrictMode, ...options });
|
||||
|
||||
export function renderHook<T>(func: () => T): { result: React.RefObject<T | null> } {
|
||||
|
||||
Reference in New Issue
Block a user