feat: Add unstable api for React 19 compitable (#51979)

* 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
pull/52051/head
二货爱吃白萝卜 1 month ago committed by GitHub
parent ee2e13786f
commit 45eeee60bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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;

@ -1 +1 @@
lint-staged
lint-staged

@ -74,6 +74,7 @@ exports[`antd exports modules correctly 1`] = `
"message",
"notification",
"theme",
"unstableSetRender",
"version",
]
`;

@ -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

@ -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…
Cancel
Save