mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-07 01:13:58 +08:00
feat: Add unstable api for React 19 compitable (#51979)
Some checks are pending
Publish Any Commit / build (push) Waiting to run
🔀 Sync mirror to Gitee / mirror (push) Waiting to run
✅ test / lint (push) Waiting to run
✅ test / test-react-legacy (16, 1/2) (push) Waiting to run
✅ test / test-react-legacy (16, 2/2) (push) Waiting to run
✅ test / test-react-legacy (17, 1/2) (push) Waiting to run
✅ test / test-react-legacy (17, 2/2) (push) Waiting to run
✅ test / test-node (push) Waiting to run
✅ test / test-react-latest (dom, 1/2) (push) Waiting to run
✅ test / test-react-latest (dom, 2/2) (push) Waiting to run
✅ test / test-react-latest-dist (dist, 1/2) (push) Blocked by required conditions
✅ test / test-react-latest-dist (dist, 2/2) (push) Blocked by required conditions
✅ test / test-react-latest-dist (dist-min, 1/2) (push) Blocked by required conditions
✅ test / test-react-latest-dist (dist-min, 2/2) (push) Blocked by required conditions
✅ test / test-coverage (push) Blocked by required conditions
✅ test / build (push) Waiting to run
✅ test / test lib/es module (es, 1/2) (push) Waiting to run
✅ test / test lib/es module (es, 2/2) (push) Waiting to run
✅ test / test lib/es module (lib, 1/2) (push) Waiting to run
✅ test / test lib/es module (lib, 2/2) (push) Waiting to run
👁️ Visual Regression Persist Start / test image (push) Waiting to run
Some checks are pending
Publish Any Commit / build (push) Waiting to run
🔀 Sync mirror to Gitee / mirror (push) Waiting to run
✅ test / lint (push) Waiting to run
✅ test / test-react-legacy (16, 1/2) (push) Waiting to run
✅ test / test-react-legacy (16, 2/2) (push) Waiting to run
✅ test / test-react-legacy (17, 1/2) (push) Waiting to run
✅ test / test-react-legacy (17, 2/2) (push) Waiting to run
✅ test / test-node (push) Waiting to run
✅ test / test-react-latest (dom, 1/2) (push) Waiting to run
✅ test / test-react-latest (dom, 2/2) (push) Waiting to run
✅ test / test-react-latest-dist (dist, 1/2) (push) Blocked by required conditions
✅ test / test-react-latest-dist (dist, 2/2) (push) Blocked by required conditions
✅ test / test-react-latest-dist (dist-min, 1/2) (push) Blocked by required conditions
✅ test / test-react-latest-dist (dist-min, 2/2) (push) Blocked by required conditions
✅ test / test-coverage (push) Blocked by required conditions
✅ test / build (push) Waiting to run
✅ test / test lib/es module (es, 1/2) (push) Waiting to run
✅ test / test lib/es module (es, 2/2) (push) Waiting to run
✅ test / test lib/es module (lib, 1/2) (push) Waiting to run
✅ test / test lib/es module (lib, 2/2) (push) Waiting to run
👁️ Visual Regression Persist Start / test image (push) Waiting to run
* chore: add unstable entrance * chore: rest of it * chore: use React 19 * chore: fix lint * chore: fix lint * chore: fix lint * chore: fix lint * chore: fix lint * chore: fix lint * chore: fix lint * chore: test ignore 19 preload * chore: bump rc-util * fix: warning of pure render * fix: warning of 19 * chore: adjust ts * test: fix test logic * test: fix test case * test: fix test case * test: fix test case * test: fix test case * test: fix test case * test: fix test case * test: fix test case * test: fix test case * chore: restore file * test: fix test case * test: fix test case * test: fix test case * test: fix test case * test: fix test case * test: update test * test: fix test case * test: update snapshot * test: fix coverage * test: fix coverage * test: add ignore image
This commit is contained in:
parent
ee2e13786f
commit
45eeee60bb
@ -66,7 +66,7 @@ const useStyle = createStyles(({ token }, markPos: [number, number, number, numb
|
||||
|
||||
export interface SemanticPreviewProps {
|
||||
semantics: { name: string; desc: string; version?: string }[];
|
||||
children: React.ReactElement;
|
||||
children: React.ReactElement<any>;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
@ -97,7 +97,7 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
|
||||
// ======================== Hover =========================
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const timerRef = React.useRef<ReturnType<typeof setTimeout>>();
|
||||
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(null);
|
||||
|
||||
const [positionMotion, setPositionMotion] = React.useState<boolean>(false);
|
||||
const [hoverSemantic, setHoverSemantic] = React.useState<string | null>(null);
|
||||
|
@ -306,7 +306,7 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
|
||||
return (
|
||||
<>
|
||||
{isValidElement(children) &&
|
||||
cloneElement(children as React.ReactElement, {
|
||||
cloneElement(children as React.ReactElement<any>, {
|
||||
onClick: () => setShow(true),
|
||||
})}
|
||||
<Drawer
|
||||
|
@ -141,13 +141,23 @@ const PrevAndNext: React.FC<{ rtl?: boolean }> = ({ rtl }) => {
|
||||
return (
|
||||
<section className={styles.prevNextNav}>
|
||||
{prev &&
|
||||
React.cloneElement(prev.label as ReactElement, {
|
||||
className: classNames(styles.pageNav, styles.prevNav, prev.className),
|
||||
})}
|
||||
React.cloneElement(
|
||||
prev.label as ReactElement<{
|
||||
className: string;
|
||||
}>,
|
||||
{
|
||||
className: classNames(styles.pageNav, styles.prevNav, prev.className),
|
||||
},
|
||||
)}
|
||||
{next &&
|
||||
React.cloneElement(next.label as ReactElement, {
|
||||
className: classNames(styles.pageNav, styles.nextNav, next.className),
|
||||
})}
|
||||
React.cloneElement(
|
||||
next.label as ReactElement<{
|
||||
className: string;
|
||||
}>,
|
||||
{
|
||||
className: classNames(styles.pageNav, styles.nextNav, next.className),
|
||||
},
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -37,7 +37,7 @@ const DocLayout: React.FC = () => {
|
||||
const location = useLocation();
|
||||
const { pathname, search, hash } = location;
|
||||
const [locale, lang] = useLocale(locales);
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout>>(null!);
|
||||
const { direction } = useContext(SiteContext);
|
||||
const { loading } = useSiteData();
|
||||
|
||||
|
@ -9,10 +9,11 @@ import {
|
||||
} from '@ant-design/cssinjs';
|
||||
import { HappyProvider } from '@ant-design/happy-work-theme';
|
||||
import { getSandpackCssText } from '@codesandbox/sandpack-react';
|
||||
import { theme as antdTheme, App } from 'antd';
|
||||
import { theme as antdTheme, App, unstableSetRender } from 'antd';
|
||||
import type { MappingAlgorithm } from 'antd';
|
||||
import type { DirectionType, ThemeConfig } from 'antd/es/config-provider';
|
||||
import { createSearchParams, useOutlet, useSearchParams, useServerInsertedHTML } from 'dumi';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { DarkContext } from '../../hooks/useDark';
|
||||
import useLayoutState from '../../hooks/useLayoutState';
|
||||
@ -30,6 +31,14 @@ type SiteState = Partial<Omit<SiteContextProps, 'updateSiteContext'>>;
|
||||
const RESPONSIVE_MOBILE = 768;
|
||||
export const ANT_DESIGN_NOT_SHOW_BANNER = 'ANT_DESIGN_NOT_SHOW_BANNER';
|
||||
|
||||
unstableSetRender((node, container) => {
|
||||
const root = createRoot(container);
|
||||
root.render(node);
|
||||
return async () => {
|
||||
root.unmount();
|
||||
};
|
||||
});
|
||||
|
||||
// const styleCache = createCache();
|
||||
// if (typeof global !== 'undefined') {
|
||||
// (global as any).styleCache = styleCache;
|
||||
|
2
.husky/pre-commit
Executable file → Normal file
2
.husky/pre-commit
Executable file → Normal file
@ -1 +1 @@
|
||||
lint-staged
|
||||
lint-staged
|
@ -74,6 +74,7 @@ exports[`antd exports modules correctly 1`] = `
|
||||
"message",
|
||||
"notification",
|
||||
"theme",
|
||||
"unstableSetRender",
|
||||
"version",
|
||||
]
|
||||
`;
|
||||
|
44
components/__tests__/unstable.test.ts
Normal file
44
components/__tests__/unstable.test.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { Modal, unstableSetRender } from 'antd';
|
||||
|
||||
import { waitFakeTimer19 } from '../../tests/utils';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('unstable', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('unstableSetRender', async () => {
|
||||
if (ReactDOM.version.startsWith('19')) {
|
||||
unstableSetRender((node, container) => {
|
||||
const root = (ReactDOM as any).createRoot(container);
|
||||
root.render(node);
|
||||
return async () => {
|
||||
root.unmount();
|
||||
};
|
||||
});
|
||||
|
||||
Modal.info({ content: 'unstableSetRender' });
|
||||
|
||||
await waitFakeTimer19();
|
||||
|
||||
expect(document.querySelector('.ant-modal')).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
@ -28,6 +28,18 @@ import { consumerBaseZIndexOffset, containerBaseZIndexOffset, useZIndex } from '
|
||||
import { resetWarned } from '../warning';
|
||||
import zIndexContext from '../zindexContext';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
const WrapWithProvider: React.FC<PropsWithChildren<{ container: ZIndexContainer }>> = ({
|
||||
children,
|
||||
container,
|
||||
|
@ -9,6 +9,18 @@ import { TARGET_CLS } from '../wave/interface';
|
||||
|
||||
(global as any).isVisible = true;
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
jest.mock('rc-util/lib/Dom/isVisible', () => {
|
||||
const mockFn = () => (global as any).isVisible;
|
||||
return mockFn;
|
||||
@ -96,6 +108,7 @@ describe('Wave component', () => {
|
||||
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
||||
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
errorSpy.mockRestore();
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
@ -2,9 +2,9 @@ import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import CSSMotion from 'rc-motion';
|
||||
import raf from 'rc-util/lib/raf';
|
||||
import { render, unmount } from 'rc-util/lib/React/render';
|
||||
import { composeRef } from 'rc-util/lib/ref';
|
||||
|
||||
import { getReactRender, type UnmountType } from '../../config-provider/UnstableContext';
|
||||
import { TARGET_CLS } from './interface';
|
||||
import type { ShowWaveEffect } from './interface';
|
||||
import { getTargetWaveColor } from './util';
|
||||
@ -17,12 +17,21 @@ export interface WaveEffectProps {
|
||||
className: string;
|
||||
target: HTMLElement;
|
||||
component?: string;
|
||||
registerUnmount: () => UnmountType | null;
|
||||
}
|
||||
|
||||
const WaveEffect: React.FC<WaveEffectProps> = (props) => {
|
||||
const { className, target, component } = props;
|
||||
const WaveEffect = (props: WaveEffectProps) => {
|
||||
const { className, target, component, registerUnmount } = props;
|
||||
const divRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
// ====================== Refs ======================
|
||||
const unmountRef = React.useRef<UnmountType>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
unmountRef.current = registerUnmount();
|
||||
}, []);
|
||||
|
||||
// ===================== Effect =====================
|
||||
const [color, setWaveColor] = React.useState<string | null>(null);
|
||||
const [borderRadius, setBorderRadius] = React.useState<number[]>([]);
|
||||
const [left, setLeft] = React.useState(0);
|
||||
@ -119,7 +128,7 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
|
||||
onAppearEnd={(_, event) => {
|
||||
if (event.deadline || (event as TransitionEvent).propertyName === 'opacity') {
|
||||
const holder = divRef.current?.parentElement!;
|
||||
unmount(holder).then(() => {
|
||||
unmountRef.current?.().then(() => {
|
||||
holder?.remove();
|
||||
});
|
||||
}
|
||||
@ -140,13 +149,6 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
|
||||
const showWaveEffect: ShowWaveEffect = (target, info) => {
|
||||
const { component } = info;
|
||||
|
||||
// Skip if not support `render` since `rc-util` render not support React 19
|
||||
// TODO: remove this check in v6
|
||||
/* istanbul ignore next */
|
||||
if (!render) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip for unchecked checkbox
|
||||
if (component === 'Checkbox' && !target.querySelector<HTMLInputElement>('input')?.checked) {
|
||||
return;
|
||||
@ -159,7 +161,18 @@ const showWaveEffect: ShowWaveEffect = (target, info) => {
|
||||
holder.style.top = '0px';
|
||||
target?.insertBefore(holder, target?.firstChild);
|
||||
|
||||
render(<WaveEffect {...info} target={target} />, holder);
|
||||
const reactRender = getReactRender();
|
||||
|
||||
let unmountCallback: UnmountType | null = null;
|
||||
|
||||
function registerUnmount() {
|
||||
return unmountCallback;
|
||||
}
|
||||
|
||||
unmountCallback = reactRender(
|
||||
<WaveEffect {...info} target={target} registerUnmount={registerUnmount} />,
|
||||
holder,
|
||||
);
|
||||
};
|
||||
|
||||
export default showWaveEffect;
|
||||
|
@ -19,7 +19,7 @@ export interface WaveProps {
|
||||
const Wave: React.FC<WaveProps> = (props) => {
|
||||
const { children, disabled, component } = props;
|
||||
const { getPrefixCls } = useContext<ConfigConsumerProps>(ConfigContext);
|
||||
const containerRef = useRef<HTMLElement>(null);
|
||||
const containerRef = useRef<HTMLElement>(null!);
|
||||
|
||||
// ============================== Style ===============================
|
||||
const prefixCls = getPrefixCls('wave');
|
||||
|
@ -28,10 +28,16 @@ const useWave = (
|
||||
const { showEffect } = wave || {};
|
||||
|
||||
// Customize wave effect
|
||||
(showEffect || showWaveEffect)(targetNode, { className, token, component, event, hashId });
|
||||
(showEffect || showWaveEffect)(targetNode, {
|
||||
className,
|
||||
token,
|
||||
component,
|
||||
event,
|
||||
hashId,
|
||||
});
|
||||
});
|
||||
|
||||
const rafId = React.useRef<number>();
|
||||
const rafId = React.useRef<number>(null);
|
||||
|
||||
// Merge trigger event into one for each frame
|
||||
const showDebounceWave: ShowWave = (event) => {
|
||||
|
@ -80,7 +80,7 @@ const Affix = React.forwardRef<AffixRef, AffixProps>((props, ref) => {
|
||||
const status = React.useRef<AffixStatus>(AFFIX_STATUS_NONE);
|
||||
|
||||
const prevTarget = React.useRef<Window | HTMLElement | null>(null);
|
||||
const prevListener = React.useRef<EventListener>();
|
||||
const prevListener = React.useRef<EventListener>(null);
|
||||
|
||||
const placeholderNodeRef = React.useRef<HTMLDivElement>(null);
|
||||
const fixedNodeRef = React.useRef<HTMLDivElement>(null);
|
||||
|
@ -76,9 +76,14 @@ const IconNode: React.FC<IconNodeProps> = (props) => {
|
||||
const iconType = iconMapFilled[type!] || null;
|
||||
if (icon) {
|
||||
return replaceElement(icon, <span className={`${prefixCls}-icon`}>{icon}</span>, () => ({
|
||||
className: classNames(`${prefixCls}-icon`, {
|
||||
[(icon as ReactElement).props.className]: (icon as ReactElement).props.className,
|
||||
}),
|
||||
className: classNames(
|
||||
`${prefixCls}-icon`,
|
||||
(
|
||||
icon as ReactElement<{
|
||||
className?: string;
|
||||
}>
|
||||
).props.className,
|
||||
),
|
||||
})) as ReactElement;
|
||||
}
|
||||
return React.createElement(iconType, { className: `${prefixCls}-icon` });
|
||||
|
@ -5,7 +5,7 @@ import { resetWarned } from 'rc-util/lib/warning';
|
||||
import Alert from '..';
|
||||
import { accessibilityTest } from '../../../tests/shared/accessibilityTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { act, render, screen, waitFakeTimer } from '../../../tests/utils';
|
||||
import { act, fireEvent, render, screen, waitFakeTimer } from '../../../tests/utils';
|
||||
import Button from '../../button';
|
||||
import Popconfirm from '../../popconfirm';
|
||||
import Tooltip from '../../tooltip';
|
||||
@ -28,7 +28,7 @@ describe('Alert', () => {
|
||||
it('should show close button and could be closed', async () => {
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const onClose = jest.fn();
|
||||
render(
|
||||
const { container } = render(
|
||||
<Alert
|
||||
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
|
||||
type="warning"
|
||||
@ -37,10 +37,7 @@ describe('Alert', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', { name: /close/i }));
|
||||
jest.runAllTimers();
|
||||
});
|
||||
fireEvent.click(container.querySelector('.ant-alert-close-icon')!);
|
||||
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
expect(errSpy).not.toHaveBeenCalled();
|
||||
|
@ -381,8 +381,6 @@ describe('Anchor Render', () => {
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
// https://github.com/testing-library/react-testing-library/releases/tag/v13.0.0
|
||||
{ legacyRoot: true },
|
||||
);
|
||||
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
@ -556,16 +554,14 @@ describe('Anchor Render', () => {
|
||||
{ key: hash2, href: `#${hash2}`, title: hash2 },
|
||||
]}
|
||||
/>,
|
||||
// https://github.com/testing-library/react-testing-library/releases/tag/v13.0.0
|
||||
{ legacyRoot: true },
|
||||
);
|
||||
|
||||
// Should be 2 times:
|
||||
// 1. ''
|
||||
// 2. hash1 (Since `getCurrentAnchor` still return same hash)
|
||||
expect(onChange).toHaveBeenCalledTimes(2);
|
||||
const calledTimes = onChange.mock.calls.length;
|
||||
fireEvent.click(container.querySelector(`a[href="#${hash2}"]`)!);
|
||||
expect(onChange).toHaveBeenCalledTimes(3);
|
||||
expect(onChange).toHaveBeenCalledTimes(calledTimes + 1);
|
||||
expect(onChange).toHaveBeenLastCalledWith(`#${hash2}`);
|
||||
});
|
||||
|
||||
|
@ -2267,15 +2267,7 @@ exports[`renders components/auto-complete/demo/render-panel.tsx extend context c
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/auto-complete/demo/render-panel.tsx extend context correctly 2`] = `
|
||||
[
|
||||
"Warning: Received \`%s\` for a non-boolean attribute \`%s\`.
|
||||
|
||||
If you want to write it to the DOM, pass a string instead: %s="%s" or %s={value.toString()}.
|
||||
|
||||
If you used to conditionally omit it with %s={condition && value}, pass %s={condition ? value : undefined} instead.%s",
|
||||
]
|
||||
`;
|
||||
exports[`renders components/auto-complete/demo/render-panel.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/auto-complete/demo/status.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { extendTest } from '../../../tests/shared/demoTest';
|
||||
|
||||
extendTest('auto-complete');
|
||||
extendTest('auto-complete', {
|
||||
skip: ['row-selection-debug.tsx'],
|
||||
});
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { imageDemoTest } from '../../../tests/shared/imageTest';
|
||||
|
||||
describe('AutoComplete image', () => {
|
||||
imageDemoTest('auto-complete');
|
||||
imageDemoTest('auto-complete', {
|
||||
skip: ['row-selection-debug.tsx'],
|
||||
});
|
||||
});
|
||||
|
@ -170,7 +170,9 @@ const RefAutoComplete = React.forwardRef<RefSelectProps, AutoCompleteProps>(
|
||||
|
||||
// We don't care debug panel
|
||||
/* istanbul ignore next */
|
||||
const PurePanel = genPurePanel(RefAutoComplete);
|
||||
const PurePanel = genPurePanel(RefAutoComplete, undefined, undefined, (props: any) =>
|
||||
omit(props, ['visible']),
|
||||
);
|
||||
|
||||
RefAutoComplete.Option = Option;
|
||||
RefAutoComplete._InternalPanelDoNotUseOrYouWillBeFired = PurePanel;
|
||||
|
@ -10,7 +10,7 @@ export interface ScrollNumberProps {
|
||||
className?: string;
|
||||
motionClassName?: string;
|
||||
count?: string | number | null;
|
||||
children?: React.ReactElement<HTMLElement>;
|
||||
children?: React.ReactElement;
|
||||
component?: React.ComponentType<any>;
|
||||
style?: React.CSSProperties;
|
||||
title?: string | number | null;
|
||||
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||
import type { GetRef } from '../../_util/type';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { act, fireEvent, render, waitFakeTimer } from '../../../tests/utils';
|
||||
import { act, fireEvent, render, waitFakeTimer19 } from '../../../tests/utils';
|
||||
import Tooltip from '../../tooltip';
|
||||
import Badge from '../index';
|
||||
|
||||
@ -50,7 +50,7 @@ describe('Badge', () => {
|
||||
const { container } = render(<Comp />);
|
||||
|
||||
fireEvent.click(container.querySelector('button')!);
|
||||
await waitFakeTimer();
|
||||
await waitFakeTimer19();
|
||||
|
||||
expect(errSpy).not.toHaveBeenCalled();
|
||||
errSpy.mockRestore();
|
||||
|
@ -4,6 +4,18 @@ import userEvent from '@testing-library/user-event';
|
||||
import Button from '..';
|
||||
import { act, fireEvent, render } from '../../../tests/utils';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
jest.mock('rc-util/lib/Dom/isVisible', () => {
|
||||
const mockFn = () => true;
|
||||
return mockFn;
|
||||
|
@ -163,7 +163,7 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
|
||||
const [hasTwoCNChar, setHasTwoCNChar] = useState<boolean>(false);
|
||||
|
||||
const buttonRef = useRef<HTMLButtonElement | HTMLAnchorElement>();
|
||||
const buttonRef = useRef<HTMLButtonElement | HTMLAnchorElement>(null);
|
||||
|
||||
const mergedRef = useComposeRef(ref, buttonRef);
|
||||
|
||||
|
@ -34,10 +34,22 @@ function splitCNCharsBySpace(child: React.ReactElement | string | number, needIn
|
||||
typeof child !== 'string' &&
|
||||
typeof child !== 'number' &&
|
||||
isString(child.type) &&
|
||||
isTwoCNChar(child.props.children)
|
||||
isTwoCNChar(
|
||||
(
|
||||
child as React.ReactElement<{
|
||||
children: string;
|
||||
}>
|
||||
).props.children,
|
||||
)
|
||||
) {
|
||||
return cloneElement(child, {
|
||||
children: child.props.children.split('').join(SPACE),
|
||||
children: (
|
||||
child as React.ReactElement<{
|
||||
children: string;
|
||||
}>
|
||||
).props.children
|
||||
.split('')
|
||||
.join(SPACE),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -153,7 +153,7 @@ export interface CalendarHeaderProps<DateType> {
|
||||
}
|
||||
function CalendarHeader<DateType>(props: CalendarHeaderProps<DateType>) {
|
||||
const { prefixCls, fullscreen, mode, onChange, onModeChange } = props;
|
||||
const divRef = React.useRef<HTMLDivElement>(null);
|
||||
const divRef = React.useRef<HTMLDivElement>(null!);
|
||||
|
||||
const formItemInputContext = useContext(FormItemInputContext);
|
||||
const mergedFormItemInputContext = useMemo(
|
||||
|
@ -121,7 +121,7 @@ const App: React.FC = () => {
|
||||
const displayHoliday = h?.getTarget() === h?.getDay() ? h?.getName() : undefined;
|
||||
if (info.type === 'date') {
|
||||
return React.cloneElement(info.originNode, {
|
||||
...info.originNode.props,
|
||||
...(info.originNode as React.ReactElement<any>).props,
|
||||
className: classNames(styles.dateCell, {
|
||||
[styles.current]: selectDate.isSame(date, 'date'),
|
||||
[styles.today]: date.isSame(dayjs(), 'date'),
|
||||
|
@ -147,7 +147,7 @@ const Card = React.forwardRef<HTMLDivElement, CardProps>((props, ref) => {
|
||||
|
||||
const isContainGrid = React.useMemo<boolean>(() => {
|
||||
let containGrid = false;
|
||||
React.Children.forEach(children as React.ReactElement, (element: JSX.Element) => {
|
||||
React.Children.forEach(children as React.ReactElement, (element: React.JSX.Element) => {
|
||||
if (element?.type === Grid) {
|
||||
containGrid = true;
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ const Carousel = React.forwardRef<CarouselRef, CarouselProps>((props, ref) => {
|
||||
...otherProps
|
||||
} = props;
|
||||
const { getPrefixCls, direction, carousel } = React.useContext(ConfigContext);
|
||||
const slickRef = React.useRef<any>();
|
||||
const slickRef = React.useRef<any>(null);
|
||||
|
||||
const goTo = (slide: number, dontAnimate = false) => {
|
||||
slickRef.current.slickGoTo(slide, dontAnimate);
|
||||
|
@ -2429,15 +2429,7 @@ exports[`renders components/cascader/demo/render-panel.tsx extend context correc
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/cascader/demo/render-panel.tsx extend context correctly 2`] = `
|
||||
[
|
||||
"Warning: Received \`%s\` for a non-boolean attribute \`%s\`.
|
||||
|
||||
If you want to write it to the DOM, pass a string instead: %s="%s" or %s={value.toString()}.
|
||||
|
||||
If you used to conditionally omit it with %s={condition && value}, pass %s={condition ? value : undefined} instead.%s",
|
||||
]
|
||||
`;
|
||||
exports[`renders components/cascader/demo/render-panel.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/cascader/demo/search.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
|
@ -370,7 +370,9 @@ if (process.env.NODE_ENV !== 'production') {
|
||||
|
||||
// We don't care debug panel
|
||||
/* istanbul ignore next */
|
||||
const PurePanel = genPurePanel(Cascader);
|
||||
const PurePanel = genPurePanel(Cascader, undefined, undefined, (props: any) =>
|
||||
omit(props, ['visible']),
|
||||
);
|
||||
|
||||
Cascader.SHOW_PARENT = SHOW_PARENT;
|
||||
Cascader.SHOW_CHILD = SHOW_CHILD;
|
||||
|
@ -111,7 +111,14 @@ const Collapse = React.forwardRef<HTMLDivElement, CollapseProps>((props, ref) =>
|
||||
/>
|
||||
);
|
||||
return cloneElement(icon, () => ({
|
||||
className: classNames((icon as React.ReactElement)?.props?.className, `${prefixCls}-arrow`),
|
||||
className: classNames(
|
||||
(
|
||||
icon as React.ReactElement<{
|
||||
className?: string;
|
||||
}>
|
||||
)?.props?.className,
|
||||
`${prefixCls}-arrow`,
|
||||
),
|
||||
}));
|
||||
},
|
||||
[mergedExpandIcon, prefixCls],
|
||||
@ -137,25 +144,30 @@ const Collapse = React.forwardRef<HTMLDivElement, CollapseProps>((props, ref) =>
|
||||
leavedClassName: `${prefixCls}-content-hidden`,
|
||||
};
|
||||
|
||||
const items = React.useMemo<React.ReactNode[] | null>(
|
||||
() =>
|
||||
children
|
||||
? toArray(children).map<React.ReactNode>((child, index) => {
|
||||
if (child.props?.disabled) {
|
||||
const key = child.key ?? String(index);
|
||||
const { disabled, collapsible } = child.props;
|
||||
const childProps: Omit<CollapseProps, 'items'> & { key: React.Key } = {
|
||||
...omit(child.props, ['disabled']),
|
||||
key,
|
||||
collapsible: collapsible ?? (disabled ? 'disabled' : undefined),
|
||||
};
|
||||
return cloneElement(child, childProps);
|
||||
}
|
||||
return child;
|
||||
})
|
||||
: null,
|
||||
[children],
|
||||
);
|
||||
const items = React.useMemo<React.ReactNode[] | null>(() => {
|
||||
if (children) {
|
||||
return toArray(children).map((child, index) => {
|
||||
const childProps = (
|
||||
child as React.ReactElement<{
|
||||
disabled?: boolean;
|
||||
collapsible?: CollapsibleType;
|
||||
}>
|
||||
).props;
|
||||
|
||||
if (childProps?.disabled) {
|
||||
const key = child.key ?? String(index);
|
||||
const mergedChildProps: Omit<CollapseProps, 'items'> & { key: React.Key } = {
|
||||
...omit(child.props as any, ['disabled']),
|
||||
key,
|
||||
collapsible: childProps.collapsible ?? 'disabled',
|
||||
};
|
||||
return cloneElement(child, mergedChildProps);
|
||||
}
|
||||
return child;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}, [children]);
|
||||
|
||||
return wrapCSSVar(
|
||||
// @ts-ignore
|
||||
|
30
components/config-provider/UnstableContext.tsx
Normal file
30
components/config-provider/UnstableContext.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import * as React from 'react';
|
||||
import { render, unmount } from 'rc-util/lib/React/render';
|
||||
|
||||
export type UnmountType = () => Promise<void>;
|
||||
export type RenderType = (
|
||||
node: React.ReactElement,
|
||||
container: Element | DocumentFragment,
|
||||
) => UnmountType;
|
||||
|
||||
const defaultReactRender: RenderType = (node, container) => {
|
||||
render(node, container);
|
||||
return () => {
|
||||
return unmount(container);
|
||||
};
|
||||
};
|
||||
|
||||
let unstableRender: RenderType = defaultReactRender;
|
||||
|
||||
/**
|
||||
* @deprecated Set React render function for compatible usage.
|
||||
* This is internal usage only compatible with React 19.
|
||||
* And will be removed in next major version.
|
||||
*/
|
||||
export function unstableSetRender(render: RenderType) {
|
||||
unstableRender = render;
|
||||
}
|
||||
|
||||
export function getReactRender() {
|
||||
return unstableRender;
|
||||
}
|
@ -12,6 +12,18 @@ import Modal from '../../modal';
|
||||
import Pagination from '../../pagination';
|
||||
import TimePicker from '../../time-picker';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('ConfigProvider.Locale', () => {
|
||||
function $$(selector: string): NodeListOf<Element> {
|
||||
return document.body.querySelectorAll(selector);
|
||||
|
@ -193,12 +193,26 @@ const Page: React.FC = () => {
|
||||
Begin Tour
|
||||
</Button>
|
||||
<Space>
|
||||
<Button ref={(node) => node && tourRefs.current.splice(0, 0, node)}> Upload</Button>
|
||||
<Button ref={(node) => node && tourRefs.current.splice(1, 0, node)} type="primary">
|
||||
<Button
|
||||
ref={(node) => {
|
||||
node && tourRefs.current.splice(0, 0, node);
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
Upload
|
||||
</Button>
|
||||
<Button
|
||||
ref={(node) => {
|
||||
node && tourRefs.current.splice(1, 0, node);
|
||||
}}
|
||||
type="primary"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
ref={(node) => node && tourRefs.current.splice(2, 0, node)}
|
||||
ref={(node) => {
|
||||
node && tourRefs.current.splice(2, 0, node);
|
||||
}}
|
||||
icon={<EllipsisOutlined />}
|
||||
/>
|
||||
</Space>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import type { JSX } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
function notEmpty(val: any) {
|
||||
|
@ -11,6 +11,9 @@ export interface DescriptionsItemProps {
|
||||
span?: number;
|
||||
}
|
||||
|
||||
const DescriptionsItem: React.FC<DescriptionsItemProps> = ({ children }) => children as JSX.Element;
|
||||
// JSX Structure Syntactic Sugar. Never reach the render code.
|
||||
/* istanbul ignore next */
|
||||
const DescriptionsItem: React.FC<DescriptionsItemProps> = ({ children }) =>
|
||||
children as React.JSX.Element;
|
||||
|
||||
export default DescriptionsItem;
|
||||
|
@ -7,7 +7,10 @@ import type { ScreenMap } from '../../_util/responsiveObserver';
|
||||
|
||||
// Convert children into items
|
||||
const transChildren2Items = (childNodes?: React.ReactNode) =>
|
||||
toArray(childNodes).map((node) => ({ ...node?.props, key: node.key }));
|
||||
toArray(childNodes).map((node) => ({
|
||||
...(node as React.ReactElement<any>)?.props,
|
||||
key: node.key,
|
||||
}));
|
||||
|
||||
export default function useItems(
|
||||
screens: ScreenMap,
|
||||
|
@ -52,7 +52,12 @@ const App: React.FC = () => {
|
||||
menu={{ items }}
|
||||
dropdownRender={(menu) => (
|
||||
<div style={contentStyle}>
|
||||
{React.cloneElement(menu as React.ReactElement, { style: menuStyle })}
|
||||
{React.cloneElement(
|
||||
menu as React.ReactElement<{
|
||||
style: React.CSSProperties;
|
||||
}>,
|
||||
{ style: menuStyle },
|
||||
)}
|
||||
<Divider style={{ margin: 0 }} />
|
||||
<Space style={{ padding: 8 }}>
|
||||
<Button type="primary">Click me!</Button>
|
||||
|
@ -178,7 +178,10 @@ const Dropdown: CompoundedComponent = (props) => {
|
||||
|
||||
const child = React.Children.only(
|
||||
isPrimitive(children) ? <span>{children}</span> : children,
|
||||
) as React.ReactElement;
|
||||
) as React.ReactElement<{
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
}>;
|
||||
|
||||
const dropdownTrigger = cloneElement(child, {
|
||||
className: classNames(
|
||||
|
@ -8,8 +8,6 @@ import { fireEvent, render, waitFakeTimer } from '../../../tests/utils';
|
||||
const { BackTop } = FloatButton;
|
||||
|
||||
describe('BackTop', () => {
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
@ -57,6 +55,8 @@ describe('BackTop', () => {
|
||||
});
|
||||
|
||||
it('no error when BackTop work', () => {
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
render(<BackTop visibilityHeight={0} />);
|
||||
expect(errSpy).not.toHaveBeenCalled();
|
||||
errSpy.mockRestore();
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import type { JSX } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Field, FieldContext, ListContext } from 'rc-field-form';
|
||||
import type { FieldProps } from 'rc-field-form/lib/Field';
|
||||
@ -165,7 +166,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
|
||||
// ========================= MISC =========================
|
||||
// Get `noStyle` required info
|
||||
const listContext = React.useContext(ListContext);
|
||||
const fieldKeyPathRef = React.useRef<InternalNamePath>();
|
||||
const fieldKeyPathRef = React.useRef<InternalNamePath>(null);
|
||||
|
||||
// ======================== Errors ========================
|
||||
// >>>>> Collect sub field errors
|
||||
@ -361,12 +362,19 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
|
||||
);
|
||||
} else if (React.isValidElement(mergedChildren)) {
|
||||
warning(
|
||||
mergedChildren.props.defaultValue === undefined,
|
||||
(
|
||||
mergedChildren as React.ReactElement<{
|
||||
defaultValue?: any;
|
||||
}>
|
||||
).props.defaultValue === undefined,
|
||||
'usage',
|
||||
'`defaultValue` will not work on controlled Field. You should use `initialValues` of Form instead.',
|
||||
);
|
||||
|
||||
const childProps = { ...mergedChildren.props, ...mergedControl };
|
||||
const childProps = {
|
||||
...(mergedChildren as React.ReactElement<any>).props,
|
||||
...mergedControl,
|
||||
};
|
||||
if (!childProps.id) {
|
||||
childProps.id = fieldId;
|
||||
}
|
||||
@ -403,7 +411,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
|
||||
triggers.forEach((eventName) => {
|
||||
childProps[eventName] = (...args: any[]) => {
|
||||
mergedControl[eventName]?.(...args);
|
||||
mergedChildren.props[eventName]?.(...args);
|
||||
(mergedChildren as React.ReactElement<any>).props[eventName]?.(...args);
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import type { JSX } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { get, set } from 'rc-util';
|
||||
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
|
||||
|
@ -10,7 +10,7 @@ import Form from '..';
|
||||
import { resetWarned } from '../../_util/warning';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { fireEvent, pureRender, render, screen, waitFakeTimer } from '../../../tests/utils';
|
||||
import { act, fireEvent, pureRender, render, screen, waitFakeTimer } from '../../../tests/utils';
|
||||
import Button from '../../button';
|
||||
import Cascader from '../../cascader';
|
||||
import Checkbox from '../../checkbox';
|
||||
@ -126,7 +126,9 @@ describe('Form', () => {
|
||||
await waitFakeTimer();
|
||||
|
||||
try {
|
||||
await form.validateFields();
|
||||
await act(async () => {
|
||||
await form.validateFields();
|
||||
});
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
@ -2244,7 +2246,7 @@ describe('Form', () => {
|
||||
await waitFakeTimer();
|
||||
|
||||
// initial validate
|
||||
const initTriggerTime = ReactVersion.startsWith('18') ? 2 : 1;
|
||||
const initTriggerTime = ReactVersion.startsWith('18') || ReactVersion.startsWith('19') ? 2 : 1;
|
||||
expect(onChange).toHaveBeenCalledTimes(initTriggerTime);
|
||||
let idx = 1;
|
||||
expect(onChange).toHaveBeenNthCalledWith(idx++, '');
|
||||
|
@ -26,7 +26,7 @@ interface ModalFormProps {
|
||||
|
||||
// reset form fields when modal is form, closed
|
||||
const useResetFormOnCloseModal = ({ form, open }: { form: FormInstance; open: boolean }) => {
|
||||
const prevOpenRef = useRef<boolean>();
|
||||
const prevOpenRef = useRef<boolean>(null);
|
||||
useEffect(() => {
|
||||
prevOpenRef.current = open;
|
||||
}, [open]);
|
||||
|
@ -177,3 +177,6 @@ export { default as Watermark } from './watermark';
|
||||
export type { WatermarkProps } from './watermark';
|
||||
export { default as Splitter } from './splitter';
|
||||
export type { SplitterProps } from './splitter';
|
||||
|
||||
// TODO: Remove in v6
|
||||
export { unstableSetRender } from './config-provider/UnstableContext';
|
||||
|
@ -8,10 +8,10 @@ import { composeRef } from 'rc-util/lib/ref';
|
||||
|
||||
import type { ConfigConsumerProps } from '../config-provider';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import DisabledContext from '../config-provider/DisabledContext';
|
||||
import useRemovePasswordTimeout from './hooks/useRemovePasswordTimeout';
|
||||
import type { InputProps, InputRef } from './Input';
|
||||
import Input from './Input';
|
||||
import DisabledContext from '../config-provider/DisabledContext';
|
||||
|
||||
const defaultIconRender = (visible: boolean): React.ReactNode =>
|
||||
visible ? <EyeOutlined /> : <EyeInvisibleOutlined />;
|
||||
@ -70,13 +70,13 @@ const Password = React.forwardRef<InputRef, PasswordProps>((props, ref) => {
|
||||
if (visible) {
|
||||
removePasswordTimeout();
|
||||
}
|
||||
setVisible((prevState) => {
|
||||
const newState = !prevState;
|
||||
if (typeof visibilityToggle === 'object') {
|
||||
visibilityToggle.onVisibleChange?.(newState);
|
||||
}
|
||||
return newState;
|
||||
});
|
||||
|
||||
const nextVisible = !visible;
|
||||
setVisible(nextVisible);
|
||||
|
||||
if (typeof visibilityToggle === 'object') {
|
||||
visibilityToggle.onVisibleChange?.(nextVisible);
|
||||
}
|
||||
};
|
||||
|
||||
const getIcon = (prefixCls: string) => {
|
||||
|
@ -98,7 +98,11 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
|
||||
button = cloneElement(enterButtonAsElement, {
|
||||
onMouseDown,
|
||||
onClick: (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
enterButtonAsElement?.props?.onClick?.(e);
|
||||
(
|
||||
enterButtonAsElement as React.ReactElement<{
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
}>
|
||||
)?.props?.onClick?.(e);
|
||||
onSearch(e);
|
||||
},
|
||||
key: 'enterButton',
|
||||
|
@ -5,7 +5,14 @@ import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
|
||||
import Input from '..';
|
||||
import focusTest from '../../../tests/shared/focusTest';
|
||||
import type { RenderOptions } from '../../../tests/utils';
|
||||
import { fireEvent, pureRender, render, triggerResize, waitFakeTimer } from '../../../tests/utils';
|
||||
import {
|
||||
fireEvent,
|
||||
pureRender,
|
||||
render,
|
||||
triggerResize,
|
||||
waitFakeTimer,
|
||||
waitFakeTimer19,
|
||||
} from '../../../tests/utils';
|
||||
import type { TextAreaRef } from '../TextArea';
|
||||
|
||||
const { TextArea } = Input;
|
||||
@ -50,15 +57,15 @@ describe('TextArea', () => {
|
||||
);
|
||||
|
||||
const { container, rerender } = pureRender(genTextArea());
|
||||
await waitFakeTimer();
|
||||
await waitFakeTimer19();
|
||||
expect(onInternalAutoSize).toHaveBeenCalledTimes(1);
|
||||
|
||||
rerender(genTextArea({ value: '1111\n2222\n3333' }));
|
||||
await waitFakeTimer();
|
||||
await waitFakeTimer19();
|
||||
expect(onInternalAutoSize).toHaveBeenCalledTimes(2);
|
||||
|
||||
rerender(genTextArea({ value: '1111' }));
|
||||
await waitFakeTimer();
|
||||
await waitFakeTimer19();
|
||||
expect(onInternalAutoSize).toHaveBeenCalledTimes(3);
|
||||
|
||||
expect(container.querySelector('textarea')?.style.overflow).toBeFalsy();
|
||||
@ -332,7 +339,6 @@ describe('TextArea allowClear', () => {
|
||||
const ref = React.createRef<TextAreaRef>();
|
||||
const { container, unmount } = render(<Input.TextArea ref={ref} autoSize />, {
|
||||
container: document.body,
|
||||
legacyRoot: true,
|
||||
} as RenderOptions);
|
||||
fireEvent.focus(container.querySelector('textarea')!);
|
||||
container.querySelector('textarea')?.focus();
|
||||
|
@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react';
|
||||
import type { InputRef } from '../Input';
|
||||
|
||||
export default function useRemovePasswordTimeout(
|
||||
inputRef: React.RefObject<InputRef>,
|
||||
inputRef: React.RefObject<InputRef | null>,
|
||||
triggerOnMount?: boolean,
|
||||
) {
|
||||
const removePasswordTimeoutRef = useRef<ReturnType<typeof setTimeout>[]>([]);
|
||||
|
@ -105,7 +105,7 @@ const Sider = React.forwardRef<HTMLDivElement, SiderProps>((props, ref) => {
|
||||
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
|
||||
|
||||
// ========================= Responsive =========================
|
||||
const responsiveHandlerRef = useRef<(mql: MediaQueryListEvent | MediaQueryList) => void>();
|
||||
const responsiveHandlerRef = useRef<(mql: MediaQueryListEvent | MediaQueryList) => void>(null);
|
||||
responsiveHandlerRef.current = (mql: MediaQueryListEvent | MediaQueryList) => {
|
||||
setBelow(mql.matches);
|
||||
onBreakpoint?.(mql.matches);
|
||||
|
@ -14,6 +14,18 @@ const Demo: React.FC<{ type: string }> = ({ type }) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('Locale Provider demo', () => {
|
||||
it('change type', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { extendTest } from '../../../tests/shared/demoTest';
|
||||
|
||||
extendTest('mentions');
|
||||
extendTest('mentions', {
|
||||
skip: ['autoSize.tsx'],
|
||||
});
|
||||
|
@ -5,7 +5,7 @@ import debounce from 'lodash/debounce';
|
||||
const App: React.FC = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [users, setUsers] = useState<{ login: string; avatar_url: string }[]>([]);
|
||||
const ref = useRef<string>();
|
||||
const ref = useRef<string>(null);
|
||||
|
||||
const loadGithubUsers = (key: string) => {
|
||||
if (!key) {
|
||||
|
@ -101,7 +101,9 @@ const MenuItem: GenericComponent = (props) => {
|
||||
>
|
||||
{cloneElement(icon, {
|
||||
className: classNames(
|
||||
React.isValidElement(icon) ? icon.props?.className : '',
|
||||
React.isValidElement(icon)
|
||||
? (icon as React.ReactElement<{ className?: string }>).props?.className
|
||||
: '',
|
||||
`${prefixCls}-item-icon`,
|
||||
),
|
||||
})}
|
||||
|
@ -44,7 +44,14 @@ export const OverrideProvider = React.forwardRef<
|
||||
return (
|
||||
<OverrideContext.Provider value={context}>
|
||||
<ContextIsolator space>
|
||||
{canRef ? React.cloneElement(children as React.ReactElement, { ref: mergedRef }) : children}
|
||||
{canRef
|
||||
? React.cloneElement(
|
||||
children as React.ReactElement<{
|
||||
ref?: React.Ref<HTMLElement>;
|
||||
}>,
|
||||
{ ref: mergedRef },
|
||||
)
|
||||
: children}
|
||||
</ContextIsolator>
|
||||
</OverrideContext.Provider>
|
||||
);
|
||||
|
@ -5,9 +5,9 @@ import omit from 'rc-util/lib/omit';
|
||||
|
||||
import { useZIndex } from '../_util/hooks/useZIndex';
|
||||
import { cloneElement } from '../_util/reactNode';
|
||||
import type { SubMenuType } from './interface';
|
||||
import type { MenuContextProps } from './MenuContext';
|
||||
import MenuContext from './MenuContext';
|
||||
import type { SubMenuType } from './interface';
|
||||
|
||||
export interface SubMenuProps extends Omit<SubMenuType, 'ref' | 'key' | 'children' | 'label'> {
|
||||
title?: React.ReactNode;
|
||||
@ -43,7 +43,9 @@ const SubMenu: React.FC<SubMenuProps> = (props) => {
|
||||
<>
|
||||
{cloneElement(icon, {
|
||||
className: classNames(
|
||||
React.isValidElement(icon) ? icon.props?.className : '',
|
||||
React.isValidElement(icon)
|
||||
? (icon as React.ReactElement<{ className?: string }>).props?.className
|
||||
: '',
|
||||
`${prefixCls}-item-icon`,
|
||||
),
|
||||
})}
|
||||
|
@ -70,18 +70,18 @@ const App: React.FC = () => {
|
||||
inlineCollapsed
|
||||
// Test only. Remove in future.
|
||||
_internalRenderMenuItem={(node) =>
|
||||
React.cloneElement(node, {
|
||||
React.cloneElement<any>(node, {
|
||||
style: {
|
||||
...node.props.style,
|
||||
...(node as any).props.style,
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
})
|
||||
}
|
||||
// Test only. Remove in future.
|
||||
_internalRenderSubMenuItem={(node) =>
|
||||
React.cloneElement(node, {
|
||||
React.cloneElement<any>(node, {
|
||||
style: {
|
||||
...node.props.style,
|
||||
...(node as any).props.style,
|
||||
background: 'rgba(255, 255, 255, 0.3)',
|
||||
},
|
||||
})
|
||||
|
@ -136,7 +136,13 @@ const InternalMenu = forwardRef<RcMenuRef, InternalMenuProps>((props, ref) => {
|
||||
return cloneElement(mergedIcon, {
|
||||
className: classNames(
|
||||
`${prefixCls}-submenu-expand-icon`,
|
||||
React.isValidElement<any>(mergedIcon) ? mergedIcon.props?.className : undefined,
|
||||
React.isValidElement<any>(mergedIcon)
|
||||
? (
|
||||
mergedIcon as React.ReactElement<{
|
||||
className?: string;
|
||||
}>
|
||||
).props?.className
|
||||
: undefined,
|
||||
),
|
||||
});
|
||||
}, [expandIcon, overrideObj?.expandIcon, menu?.expandIcon, prefixCls]);
|
||||
|
@ -6,6 +6,18 @@ import App from '../../app';
|
||||
import ConfigProvider, { defaultPrefixCls } from '../../config-provider';
|
||||
import { awaitPromise, triggerMotionEnd } from './util';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('message.config', () => {
|
||||
beforeAll(() => {
|
||||
actWrapper(act);
|
||||
|
@ -2,6 +2,18 @@ import message, { actDestroy, actWrapper } from '..';
|
||||
import { act } from '../../../tests/utils';
|
||||
import { awaitPromise, triggerMotionEnd } from './util';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('call close immediately', () => {
|
||||
beforeAll(() => {
|
||||
actWrapper(act);
|
||||
|
@ -5,6 +5,18 @@ import message, { actWrapper } from '..';
|
||||
import { act, fireEvent, waitFakeTimer } from '../../../tests/utils';
|
||||
import { awaitPromise, triggerMotionEnd } from './util';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('message', () => {
|
||||
beforeAll(() => {
|
||||
actWrapper(act);
|
||||
|
@ -1,10 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
import message, { actWrapper } from '..';
|
||||
import { act, render, waitFakeTimer } from '../../../tests/utils';
|
||||
import { act, render, waitFakeTimer, waitFakeTimer19 } from '../../../tests/utils';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
import { awaitPromise, triggerMotionEnd } from './util';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('message static warning', () => {
|
||||
beforeAll(() => {
|
||||
actWrapper(act);
|
||||
@ -32,11 +44,12 @@ describe('message static warning', () => {
|
||||
content: <div className="bamboo" />,
|
||||
duration: 0,
|
||||
});
|
||||
await waitFakeTimer();
|
||||
await waitFakeTimer19();
|
||||
|
||||
expect(document.querySelector('.bamboo')).toBeTruthy();
|
||||
|
||||
expect(errSpy).not.toHaveBeenCalled();
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('warning if use theme', async () => {
|
||||
@ -54,5 +67,6 @@ describe('message static warning', () => {
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
"Warning: [antd: message] Static function can not consume context like dynamic theme. Please use 'App' component instead.",
|
||||
);
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
@ -2,6 +2,18 @@ import message, { actWrapper } from '..';
|
||||
import { act } from '../../../tests/utils';
|
||||
import { awaitPromise, triggerMotionEnd } from './util';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('message.typescript', () => {
|
||||
beforeAll(() => {
|
||||
actWrapper(act);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { render } from 'rc-util/lib/React/render';
|
||||
|
||||
import { AppConfigContext } from '../app/context';
|
||||
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
|
||||
import { getReactRender } from '../config-provider/UnstableContext';
|
||||
import type {
|
||||
ArgsProps,
|
||||
ConfigOptions,
|
||||
@ -132,7 +132,9 @@ function flushNotice() {
|
||||
|
||||
// Delay render to avoid sync issue
|
||||
act(() => {
|
||||
render(
|
||||
const reactRender = getReactRender();
|
||||
|
||||
reactRender(
|
||||
<GlobalHolderWrapper
|
||||
ref={(node) => {
|
||||
const { instance, sync } = node || {};
|
||||
|
@ -18,6 +18,18 @@ const { confirm } = Modal;
|
||||
|
||||
jest.mock('rc-motion');
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
(global as any).injectPromise = false;
|
||||
(global as any).rejectPromise = null;
|
||||
|
||||
|
@ -14,6 +14,18 @@ import type { ModalFunc } from '../confirm';
|
||||
jest.mock('rc-util/lib/Portal');
|
||||
jest.mock('rc-motion');
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('Modal.hook', () => {
|
||||
// Inject CSSMotion to replace with No transition support
|
||||
const MockCSSMotion = genCSSMotion(false);
|
||||
|
@ -2,9 +2,21 @@ import * as React from 'react';
|
||||
|
||||
import Modal from '..';
|
||||
import { resetWarned } from '../../_util/warning';
|
||||
import { render, waitFakeTimer } from '../../../tests/utils';
|
||||
import { render, waitFakeTimer, waitFakeTimer19 } from '../../../tests/utils';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('Modal.confirm warning', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
@ -25,11 +37,12 @@ describe('Modal.confirm warning', () => {
|
||||
Modal.confirm({
|
||||
content: <div className="bamboo" />,
|
||||
});
|
||||
await waitFakeTimer();
|
||||
await waitFakeTimer19();
|
||||
|
||||
expect(document.querySelector('.bamboo')).toBeTruthy();
|
||||
|
||||
expect(errSpy).not.toHaveBeenCalled();
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('warning if use theme', async () => {
|
||||
@ -46,5 +59,6 @@ describe('Modal.confirm warning', () => {
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
"Warning: [antd: Modal] Static function can not consume context like dynamic theme. Please use 'App' component instead.",
|
||||
);
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
@ -11,12 +11,7 @@ export interface ConfirmCancelBtnProps
|
||||
'cancelButtonProps' | 'isSilent' | 'rootPrefixCls' | 'close' | 'onConfirm' | 'onCancel'
|
||||
> {
|
||||
autoFocusButton?: false | 'ok' | 'cancel' | null;
|
||||
cancelTextLocale?:
|
||||
| string
|
||||
| number
|
||||
| true
|
||||
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
||||
| Iterable<React.ReactNode>;
|
||||
cancelTextLocale?: React.ReactNode;
|
||||
mergedOkCancel?: boolean;
|
||||
}
|
||||
|
||||
|
@ -11,12 +11,7 @@ export interface ConfirmOkBtnProps
|
||||
'close' | 'isSilent' | 'okType' | 'okButtonProps' | 'rootPrefixCls' | 'onConfirm' | 'onOk'
|
||||
> {
|
||||
autoFocusButton?: false | 'ok' | 'cancel' | null;
|
||||
okTextLocale?:
|
||||
| string
|
||||
| number
|
||||
| true
|
||||
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
||||
| Iterable<React.ReactNode>;
|
||||
okTextLocale?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ConfirmOkBtn: FC = () => {
|
||||
|
@ -6,12 +6,7 @@ import { ModalContext } from '../context';
|
||||
import type { ModalProps } from '../interface';
|
||||
|
||||
export interface NormalCancelBtnProps extends Pick<ModalProps, 'cancelButtonProps' | 'onCancel'> {
|
||||
cancelTextLocale?:
|
||||
| string
|
||||
| number
|
||||
| true
|
||||
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
||||
| Iterable<React.ReactNode>;
|
||||
cancelTextLocale?: React.ReactNode;
|
||||
}
|
||||
|
||||
const NormalCancelBtn: FC = () => {
|
||||
|
@ -8,12 +8,7 @@ import type { ModalProps } from '../interface';
|
||||
|
||||
export interface NormalOkBtnProps
|
||||
extends Pick<ModalProps, 'confirmLoading' | 'okType' | 'okButtonProps' | 'onOk'> {
|
||||
okTextLocale?:
|
||||
| string
|
||||
| number
|
||||
| true
|
||||
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
||||
| Iterable<React.ReactNode>;
|
||||
okTextLocale?: React.ReactNode;
|
||||
}
|
||||
|
||||
const NormalOkBtn: FC = () => {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { render as reactRender, unmount as reactUnmount } from 'rc-util/lib/React/render';
|
||||
|
||||
import warning from '../_util/warning';
|
||||
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
|
||||
import { getReactRender, UnmountType } from '../config-provider/UnstableContext';
|
||||
import type { ConfirmDialogProps } from './ConfirmDialog';
|
||||
import ConfirmDialog from './ConfirmDialog';
|
||||
import destroyFns from './destroyFns';
|
||||
@ -71,6 +71,8 @@ export default function confirm(config: ModalFuncProps) {
|
||||
let currentConfig = { ...config, close, open: true } as any;
|
||||
let timeoutId: ReturnType<typeof setTimeout>;
|
||||
|
||||
let reactUnmount: UnmountType;
|
||||
|
||||
function destroy(...args: any[]) {
|
||||
const triggerCancel = args.some((param) => param?.triggerCancel);
|
||||
if (triggerCancel) {
|
||||
@ -84,7 +86,7 @@ export default function confirm(config: ModalFuncProps) {
|
||||
}
|
||||
}
|
||||
|
||||
reactUnmount(container);
|
||||
reactUnmount();
|
||||
}
|
||||
|
||||
function render(props: any) {
|
||||
@ -102,7 +104,9 @@ export default function confirm(config: ModalFuncProps) {
|
||||
|
||||
const dom = <ConfirmDialogWrapper {...props} />;
|
||||
|
||||
reactRender(
|
||||
const reactRender = getReactRender();
|
||||
|
||||
reactUnmount = reactRender(
|
||||
<ConfigProvider prefixCls={rootPrefixCls} iconPrefixCls={iconPrefixCls} theme={theme}>
|
||||
{global.holderRender ? global.holderRender(dom) : dom}
|
||||
</ConfigProvider>,
|
||||
|
@ -7,7 +7,7 @@ const App: React.FC = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [disabled, setDisabled] = useState(true);
|
||||
const [bounds, setBounds] = useState({ left: 0, top: 0, bottom: 0, right: 0 });
|
||||
const draggleRef = useRef<HTMLDivElement>(null);
|
||||
const draggleRef = useRef<HTMLDivElement>(null!);
|
||||
|
||||
const showModal = () => {
|
||||
setOpen(true);
|
||||
|
@ -51,7 +51,7 @@ export const Footer: React.FC<
|
||||
const [locale] = useLocale('Modal', getConfirmLocale());
|
||||
|
||||
// ================== Locale Text ==================
|
||||
const okTextLocale = okText || locale?.okText;
|
||||
const okTextLocale: React.ReactNode = okText || locale?.okText;
|
||||
const cancelTextLocale = cancelText || locale?.cancelText;
|
||||
|
||||
// ================= Context Value =================
|
||||
|
@ -6,6 +6,18 @@ import App from '../../app';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
import { awaitPromise, triggerMotionEnd } from './util';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('notification.config', () => {
|
||||
beforeAll(() => {
|
||||
actWrapper(act);
|
||||
|
@ -6,6 +6,18 @@ import { act, fireEvent } from '../../../tests/utils';
|
||||
import ConfigProvider, { defaultPrefixCls } from '../../config-provider';
|
||||
import { awaitPromise, triggerMotionEnd } from './util';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('notification', () => {
|
||||
beforeAll(() => {
|
||||
actWrapper(act);
|
||||
|
@ -3,6 +3,18 @@ import { act, fireEvent } from '../../../tests/utils';
|
||||
import type { ArgsProps, GlobalConfigProps } from '../interface';
|
||||
import { awaitPromise, triggerMotionEnd } from './util';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('Notification.placement', () => {
|
||||
function open(args?: Partial<ArgsProps>) {
|
||||
notification.open({
|
||||
|
@ -1,10 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
import notification, { actWrapper } from '..';
|
||||
import { act, render, waitFakeTimer } from '../../../tests/utils';
|
||||
import { act, render, waitFakeTimer, waitFakeTimer19 } from '../../../tests/utils';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
import { awaitPromise, triggerMotionEnd } from './util';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('notification static warning', () => {
|
||||
beforeAll(() => {
|
||||
actWrapper(act);
|
||||
@ -32,11 +44,12 @@ describe('notification static warning', () => {
|
||||
message: <div className="bamboo" />,
|
||||
duration: 0,
|
||||
});
|
||||
await waitFakeTimer();
|
||||
await waitFakeTimer19();
|
||||
|
||||
expect(document.querySelector('.bamboo')).toBeTruthy();
|
||||
|
||||
expect(errSpy).not.toHaveBeenCalled();
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('warning if use theme', async () => {
|
||||
@ -54,5 +67,6 @@ describe('notification static warning', () => {
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
"Warning: [antd: notification] Static function can not consume context like dynamic theme. Please use 'App' component instead.",
|
||||
);
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { render } from 'rc-util/lib/React/render';
|
||||
|
||||
import { AppConfigContext } from '../app/context';
|
||||
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
|
||||
import { getReactRender } from '../config-provider/UnstableContext';
|
||||
import type { ArgsProps, GlobalConfigProps, NotificationInstance } from './interface';
|
||||
import PurePanel from './PurePanel';
|
||||
import useNotification, { useInternalNotification } from './useNotification';
|
||||
@ -126,7 +126,9 @@ function flushNotice() {
|
||||
|
||||
// Delay render to avoid sync issue
|
||||
act(() => {
|
||||
render(
|
||||
const reactRender = getReactRender();
|
||||
|
||||
reactRender(
|
||||
<GlobalHolderWrapper
|
||||
ref={(node) => {
|
||||
const { instance, sync } = node || {};
|
||||
|
@ -7,6 +7,18 @@ import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { act, fireEvent, render, waitFakeTimer } from '../../../tests/utils';
|
||||
import Button from '../../button';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('Popconfirm', () => {
|
||||
mountTest(() => <Popconfirm title="test" />);
|
||||
rtlTest(() => <Popconfirm title="test" />);
|
||||
|
@ -95,7 +95,11 @@ const InternalPopover = React.forwardRef<TooltipRef, PopoverProps>((props, ref)
|
||||
{cloneElement(children, {
|
||||
onKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (React.isValidElement(children)) {
|
||||
children?.props.onKeyDown?.(e);
|
||||
(
|
||||
children as React.ReactElement<{
|
||||
onKeyDown: React.KeyboardEventHandler<HTMLDivElement>;
|
||||
}>
|
||||
)?.props.onKeyDown?.(e);
|
||||
}
|
||||
onKeyDown(e);
|
||||
},
|
||||
|
@ -4,7 +4,7 @@ import raf from 'rc-util/lib/raf';
|
||||
export default function useRafLock(): [state: boolean, setState: (nextState: boolean) => void] {
|
||||
const [state, setState] = React.useState(false);
|
||||
|
||||
const rafRef = React.useRef<number>();
|
||||
const rafRef = React.useRef<number>(null);
|
||||
const cleanup = () => {
|
||||
raf.cancel(rafRef.current!);
|
||||
};
|
||||
|
@ -16,7 +16,10 @@ export default function Indicator(props: IndicatorProps) {
|
||||
|
||||
if (indicator && React.isValidElement(indicator)) {
|
||||
return cloneElement(indicator, {
|
||||
className: classNames(indicator.props.className, dotClassName),
|
||||
className: classNames(
|
||||
(indicator as React.ReactElement<{ className?: string }>).props.className,
|
||||
dotClassName,
|
||||
),
|
||||
percent,
|
||||
});
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { Flex, Spin, Switch } from 'antd';
|
||||
const App: React.FC = () => {
|
||||
const [auto, setAuto] = React.useState(false);
|
||||
const [percent, setPercent] = React.useState(-50);
|
||||
const timerRef = React.useRef<ReturnType<typeof setTimeout>>();
|
||||
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
timerRef.current = setTimeout(() => {
|
||||
@ -13,7 +13,7 @@ const App: React.FC = () => {
|
||||
return nextPercent > 150 ? -50 : nextPercent;
|
||||
});
|
||||
}, 100);
|
||||
return () => clearTimeout(timerRef.current);
|
||||
return () => clearTimeout(timerRef.current!);
|
||||
}, [percent]);
|
||||
|
||||
const mergedPercent = auto ? 'auto' : percent;
|
||||
|
@ -12,7 +12,7 @@ export default function usePercent(
|
||||
percent?: number | 'auto',
|
||||
): number | undefined {
|
||||
const [mockPercent, setMockPercent] = React.useState(0);
|
||||
const mockIntervalRef = React.useRef<ReturnType<typeof setInterval>>();
|
||||
const mockIntervalRef = React.useRef<ReturnType<typeof setInterval>>(null);
|
||||
|
||||
const isAuto = percent === 'auto';
|
||||
|
||||
@ -38,7 +38,7 @@ export default function usePercent(
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearInterval(mockIntervalRef.current);
|
||||
clearInterval(mockIntervalRef.current!);
|
||||
};
|
||||
}, [isAuto, spinning]);
|
||||
|
||||
|
@ -18,9 +18,9 @@ export default function useLegacyItems(items?: StepProps[], children?: React.Rea
|
||||
return items;
|
||||
}
|
||||
|
||||
const childrenItems = toArray(children).map((node: React.ReactElement<StepProps>) => {
|
||||
const childrenItems = toArray(children).map((node) => {
|
||||
if (React.isValidElement(node)) {
|
||||
const { props } = node;
|
||||
const { props } = node as React.ReactElement<StepProps>;
|
||||
const item: StepProps = {
|
||||
...props,
|
||||
};
|
||||
|
@ -11,6 +11,18 @@ jest.mock('rc-util/lib/Dom/isVisible', () => {
|
||||
return mockFn;
|
||||
});
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('Switch', () => {
|
||||
focusTest(Switch, { refFocus: true });
|
||||
mountTest(Switch);
|
||||
|
@ -222,7 +222,7 @@ const InternalTable = <RecordType extends AnyObject = AnyObject>(
|
||||
}, [rawData]);
|
||||
|
||||
const internalRefs: NonNullable<RcTableProps['internalRefs']> = {
|
||||
body: React.useRef<HTMLDivElement>(),
|
||||
body: React.useRef<HTMLDivElement>(null),
|
||||
} as NonNullable<RcTableProps['internalRefs']>;
|
||||
|
||||
// ============================ Width =============================
|
||||
|
@ -3125,7 +3125,7 @@ describe('Table.filter', () => {
|
||||
|
||||
fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
|
||||
expect(dropdownRender).toHaveBeenCalled();
|
||||
expect(dropdownRender.mock.calls[0][0]).toMatchSnapshot();
|
||||
expect(React.isValidElement(dropdownRender.mock.calls[0][0])).toBeTruthy();
|
||||
expect(getByText('Foo')).toBeTruthy();
|
||||
});
|
||||
|
||||
|
@ -838,126 +838,3 @@ exports[`Table.filter renders radio filter correctly 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Table.filter should support filterDropdownProps dropdownRender 1`] = `
|
||||
<FilterDropdownMenuWrapper
|
||||
className="ant-table-filter-dropdown"
|
||||
>
|
||||
<React.Fragment>
|
||||
<React.Fragment>
|
||||
<FilterSearch
|
||||
filterSearch={false}
|
||||
locale={
|
||||
{
|
||||
"cancelSort": "Click to cancel sorting",
|
||||
"collapse": "Collapse row",
|
||||
"emptyText": "No data",
|
||||
"expand": "Expand row",
|
||||
"filterCheckall": "Select all items",
|
||||
"filterConfirm": "OK",
|
||||
"filterEmptyText": "No filters",
|
||||
"filterReset": "Reset",
|
||||
"filterSearchPlaceholder": "Search in filters",
|
||||
"filterTitle": "Filter menu",
|
||||
"selectAll": "Select current page",
|
||||
"selectInvert": "Invert current page",
|
||||
"selectNone": "Clear all data",
|
||||
"selectionAll": "Select all data",
|
||||
"sortTitle": "Sort",
|
||||
"triggerAsc": "Click to sort ascending",
|
||||
"triggerDesc": "Click to sort descending",
|
||||
}
|
||||
}
|
||||
onChange={[Function]}
|
||||
tablePrefixCls="ant-table"
|
||||
value=""
|
||||
/>
|
||||
<Menu
|
||||
className=""
|
||||
items={
|
||||
[
|
||||
{
|
||||
"key": "boy",
|
||||
"label": <React.Fragment>
|
||||
<Checkbox
|
||||
checked={false}
|
||||
/>
|
||||
<span>
|
||||
Boy
|
||||
</span>
|
||||
</React.Fragment>,
|
||||
},
|
||||
{
|
||||
"key": "girl",
|
||||
"label": <React.Fragment>
|
||||
<Checkbox
|
||||
checked={false}
|
||||
/>
|
||||
<span>
|
||||
Girl
|
||||
</span>
|
||||
</React.Fragment>,
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"key": "designer",
|
||||
"label": <React.Fragment>
|
||||
<Checkbox
|
||||
checked={false}
|
||||
/>
|
||||
<span>
|
||||
Designer
|
||||
</span>
|
||||
</React.Fragment>,
|
||||
},
|
||||
{
|
||||
"key": "coder",
|
||||
"label": <React.Fragment>
|
||||
<Checkbox
|
||||
checked={false}
|
||||
/>
|
||||
<span>
|
||||
Coder
|
||||
</span>
|
||||
</React.Fragment>,
|
||||
},
|
||||
],
|
||||
"key": "title",
|
||||
"label": "Title",
|
||||
"popupClassName": "ant-table-filter-dropdown-submenu",
|
||||
},
|
||||
]
|
||||
}
|
||||
multiple={true}
|
||||
onDeselect={[Function]}
|
||||
onOpenChange={[Function]}
|
||||
onSelect={[Function]}
|
||||
openKeys={[]}
|
||||
prefixCls="ant-dropdown-menu"
|
||||
selectable={true}
|
||||
selectedKeys={[]}
|
||||
/>
|
||||
</React.Fragment>
|
||||
<div
|
||||
className="ant-table-filter-dropdown-btns"
|
||||
>
|
||||
<Button
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
size="small"
|
||||
type="link"
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
onClick={[Function]}
|
||||
size="small"
|
||||
type="primary"
|
||||
>
|
||||
OK
|
||||
</Button>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</FilterDropdownMenuWrapper>
|
||||
`;
|
||||
|
@ -1500,7 +1500,7 @@ Array [
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value=""
|
||||
value="unset"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
@ -1592,7 +1592,7 @@ Array [
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value=""
|
||||
value="unset"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
@ -6049,7 +6049,7 @@ Array [
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value=""
|
||||
value="unset"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
@ -6141,7 +6141,7 @@ Array [
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value=""
|
||||
value="unset"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
|
@ -1500,6 +1500,7 @@ Array [
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="unset"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
@ -1591,6 +1592,7 @@ Array [
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="unset"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
@ -5370,6 +5372,7 @@ Array [
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="unset"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
@ -5461,6 +5464,7 @@ Array [
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="unset"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
|
@ -9,5 +9,9 @@ extendTest('table', {
|
||||
'fixed-gapped-columns.tsx',
|
||||
'fixed-columns.tsx',
|
||||
'fixed-columns-header.tsx',
|
||||
'row-selection-debug.tsx',
|
||||
'drag-sorting.tsx',
|
||||
'drag-sorting-handler.tsx',
|
||||
'drag-column-sorting.tsx',
|
||||
],
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { imageDemoTest } from '../../../tests/shared/imageTest';
|
||||
|
||||
describe('Table image', () => {
|
||||
imageDemoTest('table', { skip: ['virtual-list.tsx'] });
|
||||
imageDemoTest('table', { skip: ['virtual-list.tsx', 'row-selection-debug.tsx'] });
|
||||
});
|
||||
|
@ -88,12 +88,12 @@ const App: React.FC = () => {
|
||||
const [showFooter, setShowFooter] = useState(true);
|
||||
const [rowSelection, setRowSelection] = useState<TableRowSelection<DataType> | undefined>({});
|
||||
const [hasData, setHasData] = useState(true);
|
||||
const [tableLayout, setTableLayout] = useState();
|
||||
const [tableLayout, setTableLayout] = useState<string>('unset');
|
||||
const [top, setTop] = useState<TablePaginationPosition>('none');
|
||||
const [bottom, setBottom] = useState<TablePaginationPosition>('bottomRight');
|
||||
const [ellipsis, setEllipsis] = useState(false);
|
||||
const [yScroll, setYScroll] = useState(false);
|
||||
const [xScroll, setXScroll] = useState<string>();
|
||||
const [xScroll, setXScroll] = useState<string>('unset');
|
||||
|
||||
const handleBorderChange = (enable: boolean) => {
|
||||
setBordered(enable);
|
||||
@ -151,7 +151,7 @@ const App: React.FC = () => {
|
||||
if (yScroll) {
|
||||
scroll.y = 240;
|
||||
}
|
||||
if (xScroll) {
|
||||
if (xScroll !== 'unset') {
|
||||
scroll.x = '100vw';
|
||||
}
|
||||
|
||||
@ -171,7 +171,7 @@ const App: React.FC = () => {
|
||||
footer: showFooter ? defaultFooter : undefined,
|
||||
rowSelection,
|
||||
scroll,
|
||||
tableLayout,
|
||||
tableLayout: tableLayout === 'unset' ? undefined : (tableLayout as TableProps['tableLayout']),
|
||||
};
|
||||
|
||||
return (
|
||||
@ -216,14 +216,14 @@ const App: React.FC = () => {
|
||||
</Form.Item>
|
||||
<Form.Item label="Table Scroll">
|
||||
<Radio.Group value={xScroll} onChange={handleXScrollChange}>
|
||||
<Radio.Button value={undefined}>Unset</Radio.Button>
|
||||
<Radio.Button value="unset">Unset</Radio.Button>
|
||||
<Radio.Button value="scroll">Scroll</Radio.Button>
|
||||
<Radio.Button value="fixed">Fixed Columns</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="Table Layout">
|
||||
<Radio.Group value={tableLayout} onChange={handleTableLayoutChange}>
|
||||
<Radio.Button value={undefined}>Unset</Radio.Button>
|
||||
<Radio.Button value="unset">Unset</Radio.Button>
|
||||
<Radio.Button value="fixed">Fixed</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
@ -86,12 +86,12 @@ const App: React.FC = () => {
|
||||
const [showFooter, setShowFooter] = useState(true);
|
||||
const [rowSelection, setRowSelection] = useState<TableRowSelection<DataType> | undefined>({});
|
||||
const [hasData, setHasData] = useState(true);
|
||||
const [tableLayout, setTableLayout] = useState();
|
||||
const [tableLayout, setTableLayout] = useState<string>('unset');
|
||||
const [top, setTop] = useState<TablePaginationPosition>('none');
|
||||
const [bottom, setBottom] = useState<TablePaginationPosition>('bottomRight');
|
||||
const [ellipsis, setEllipsis] = useState(false);
|
||||
const [yScroll, setYScroll] = useState(false);
|
||||
const [xScroll, setXScroll] = useState<string>();
|
||||
const [xScroll, setXScroll] = useState<string>('unset');
|
||||
|
||||
const handleBorderChange = (enable: boolean) => {
|
||||
setBordered(enable);
|
||||
@ -149,7 +149,7 @@ const App: React.FC = () => {
|
||||
if (yScroll) {
|
||||
scroll.y = 240;
|
||||
}
|
||||
if (xScroll) {
|
||||
if (xScroll !== 'unset') {
|
||||
scroll.x = '100vw';
|
||||
}
|
||||
|
||||
@ -169,7 +169,7 @@ const App: React.FC = () => {
|
||||
footer: showFooter ? defaultFooter : undefined,
|
||||
rowSelection,
|
||||
scroll,
|
||||
tableLayout,
|
||||
tableLayout: tableLayout === 'unset' ? undefined : (tableLayout as TableProps['tableLayout']),
|
||||
};
|
||||
|
||||
return (
|
||||
@ -214,14 +214,14 @@ const App: React.FC = () => {
|
||||
</Form.Item>
|
||||
<Form.Item label="Table Scroll">
|
||||
<Radio.Group value={xScroll} onChange={handleXScrollChange}>
|
||||
<Radio.Button value={undefined}>Unset</Radio.Button>
|
||||
<Radio.Button value="unset">Unset</Radio.Button>
|
||||
<Radio.Button value="scroll">Scroll</Radio.Button>
|
||||
<Radio.Button value="fixed">Fixed Columns</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="Table Layout">
|
||||
<Radio.Group value={tableLayout} onChange={handleTableLayoutChange}>
|
||||
<Radio.Button value={undefined}>Unset</Radio.Button>
|
||||
<Radio.Button value="unset">Unset</Radio.Button>
|
||||
<Radio.Button value="fixed">Fixed</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { extendTest } from '../../../tests/shared/demoTest';
|
||||
|
||||
extendTest('tabs');
|
||||
extendTest('tabs', {
|
||||
skip: ['custom-tab-bar-node.tsx'],
|
||||
});
|
||||
|
@ -27,7 +27,7 @@ const DraggableTabNode: React.FC<Readonly<DraggableTabPaneProps>> = ({ className
|
||||
cursor: 'move',
|
||||
};
|
||||
|
||||
return React.cloneElement(props.children as React.ReactElement, {
|
||||
return React.cloneElement(props.children as React.ReactElement<any>, {
|
||||
ref: setNodeRef,
|
||||
style,
|
||||
...attributes,
|
||||
@ -62,7 +62,10 @@ const App: React.FC = () => {
|
||||
<SortableContext items={items.map((i) => i.key)} strategy={horizontalListSortingStrategy}>
|
||||
<DefaultTabBar {...tabBarProps}>
|
||||
{(node) => (
|
||||
<DraggableTabNode {...node.props} key={node.key}>
|
||||
<DraggableTabNode
|
||||
{...(node as React.ReactElement<DraggableTabPaneProps>).props}
|
||||
key={node.key}
|
||||
>
|
||||
{node}
|
||||
</DraggableTabNode>
|
||||
)}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user