mirror of
https://github.com/ant-design/ant-design.git
synced 2026-02-09 10:59:19 +08:00
Compare commits
13 Commits
feature
...
fix-slider
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
414238a2f7 | ||
|
|
1219564b2d | ||
|
|
2ed9896fd0 | ||
|
|
62d41646dc | ||
|
|
b17f8450c4 | ||
|
|
00a2e91472 | ||
|
|
8fe54617ed | ||
|
|
254691e1d9 | ||
|
|
4a3f5bece9 | ||
|
|
29f158c393 | ||
|
|
f1b508073b | ||
|
|
23f2061d73 | ||
|
|
727a547d59 |
@@ -1,10 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import raf from '@rc-component/util/lib/raf';
|
||||
import { composeRef } from '@rc-component/util/lib/ref';
|
||||
import type { SliderRef } from '@rc-component/slider/lib/Slider';
|
||||
|
||||
import type { TooltipProps } from '../tooltip';
|
||||
import type { TooltipPlacement, TooltipProps } from '../tooltip';
|
||||
import Tooltip from '../tooltip';
|
||||
|
||||
export type SliderTooltipProps = TooltipProps & {
|
||||
@@ -13,8 +13,9 @@ export type SliderTooltipProps = TooltipProps & {
|
||||
};
|
||||
|
||||
const SliderTooltip = React.forwardRef<SliderRef, SliderTooltipProps>((props, ref) => {
|
||||
const { open, draggingDelete, value } = props;
|
||||
const { open, draggingDelete, value, placement, getPopupContainer } = props;
|
||||
const innerRef = useRef<any>(null);
|
||||
const [adjustedPlacement, setAdjustedPlacement] = useState<TooltipPlacement | undefined>(placement);
|
||||
|
||||
const mergedOpen = open && !draggingDelete;
|
||||
|
||||
@@ -32,17 +33,123 @@ const SliderTooltip = React.forwardRef<SliderRef, SliderTooltipProps>((props, re
|
||||
});
|
||||
}
|
||||
|
||||
// Check if tooltip overflows container and adjust placement
|
||||
const checkAndAdjustPlacement = React.useCallback(() => {
|
||||
const tooltipRef = innerRef.current;
|
||||
|
||||
const popupElement = tooltipRef.popupElement;
|
||||
const triggerElement = tooltipRef.nativeElement;
|
||||
|
||||
if (!popupElement || !triggerElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get container
|
||||
const container = getPopupContainer
|
||||
? getPopupContainer(triggerElement)
|
||||
: document.body;
|
||||
|
||||
// Get container boundaries
|
||||
const containerRect = container === document.body
|
||||
? { left: 0, top: 0, right: window.innerWidth, bottom: window.innerHeight }
|
||||
: container.getBoundingClientRect();
|
||||
|
||||
// Get tooltip position
|
||||
const popupRect = popupElement.getBoundingClientRect();
|
||||
|
||||
// Get original placement
|
||||
const originalPlacement = placement || 'top';
|
||||
|
||||
// Check if tooltip overflows container
|
||||
const isOverflowLeft = popupRect.left < containerRect.left;
|
||||
const isOverflowRight = popupRect.right > containerRect.right;
|
||||
const isOverflowTop = popupRect.top < containerRect.top;
|
||||
const isOverflowBottom = popupRect.bottom > containerRect.bottom;
|
||||
|
||||
// If original placement is top or bottom (horizontal mode), check horizontal overflow
|
||||
if (originalPlacement === 'top' || originalPlacement === 'bottom') {
|
||||
if (isOverflowLeft) {
|
||||
// Left overflow, change to right placement
|
||||
setAdjustedPlacement('right');
|
||||
} else if (isOverflowRight) {
|
||||
// Right overflow, change to left placement
|
||||
setAdjustedPlacement('left');
|
||||
} else {
|
||||
// No horizontal overflow, restore original placement
|
||||
setAdjustedPlacement(placement);
|
||||
}
|
||||
}
|
||||
// If original placement is left or right (vertical mode), check vertical overflow
|
||||
else if (originalPlacement === 'left' || originalPlacement === 'right') {
|
||||
if (isOverflowTop) {
|
||||
// Top overflow, change to bottom placement
|
||||
setAdjustedPlacement('bottom');
|
||||
} else if (isOverflowBottom) {
|
||||
// Bottom overflow, change to top placement
|
||||
setAdjustedPlacement('top');
|
||||
} else {
|
||||
// No vertical overflow, restore original placement
|
||||
setAdjustedPlacement(placement);
|
||||
}
|
||||
}
|
||||
}, [mergedOpen, placement, getPopupContainer]);
|
||||
|
||||
const adjustPlacementRef = useRef<number | null>(null);
|
||||
|
||||
const cancelAdjustPlacement = React.useCallback(() => {
|
||||
if (adjustPlacementRef.current !== null) {
|
||||
raf.cancel(adjustPlacementRef.current);
|
||||
adjustPlacementRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const scheduleAdjustPlacement = React.useCallback(() => {
|
||||
cancelAdjustPlacement();
|
||||
adjustPlacementRef.current = raf(() => {
|
||||
// Use raf again to ensure tooltip is positioned
|
||||
adjustPlacementRef.current = raf(() => {
|
||||
checkAndAdjustPlacement();
|
||||
adjustPlacementRef.current = null;
|
||||
});
|
||||
});
|
||||
}, [cancelAdjustPlacement, checkAndAdjustPlacement]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (mergedOpen) {
|
||||
keepAlign();
|
||||
scheduleAdjustPlacement();
|
||||
return () => {
|
||||
cancelKeepAlign();
|
||||
cancelAdjustPlacement();
|
||||
};
|
||||
} else {
|
||||
cancelKeepAlign();
|
||||
cancelAdjustPlacement();
|
||||
// Reset placement when closed
|
||||
setAdjustedPlacement(placement);
|
||||
}
|
||||
}, [mergedOpen, props.title, value, placement, scheduleAdjustPlacement, cancelAdjustPlacement]);
|
||||
|
||||
// Listen to tooltip position changes
|
||||
React.useEffect(() => {
|
||||
if (!mergedOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
return cancelKeepAlign;
|
||||
}, [mergedOpen, props.title, value]);
|
||||
const handleResize = () => {
|
||||
scheduleAdjustPlacement();
|
||||
};
|
||||
|
||||
return <Tooltip ref={composeRef(innerRef, ref)} {...props} open={mergedOpen} />;
|
||||
window.addEventListener('resize', handleResize);
|
||||
window.addEventListener('scroll', handleResize, true);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
window.removeEventListener('scroll', handleResize, true);
|
||||
};
|
||||
}, [mergedOpen, scheduleAdjustPlacement]);
|
||||
|
||||
return <Tooltip ref={composeRef(innerRef, ref)} {...props} open={mergedOpen} placement={adjustedPlacement} />;
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
|
||||
@@ -17,9 +17,61 @@ jest.mock('../../tooltip', () => {
|
||||
const ReactReal: typeof React = jest.requireActual('react');
|
||||
const Tooltip = jest.requireActual('../../tooltip');
|
||||
const TooltipComponent = Tooltip.default;
|
||||
|
||||
// Create mock default element factory inside mock
|
||||
const createMockElement = (): HTMLElement => ({
|
||||
getBoundingClientRect: jest.fn(() => ({
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
toJSON: jest.fn(),
|
||||
})),
|
||||
} as any);
|
||||
|
||||
const createMockDivElement = (): HTMLDivElement => ({
|
||||
getBoundingClientRect: jest.fn(() => ({
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
toJSON: jest.fn(),
|
||||
})),
|
||||
} as any);
|
||||
|
||||
return ReactReal.forwardRef<TooltipRef, TooltipProps>((props, ref) => {
|
||||
(global as any).tooltipProps = props;
|
||||
return <TooltipComponent {...props} ref={ref} />;
|
||||
const internalRef = ReactReal.useRef<TooltipRef>(null);
|
||||
|
||||
ReactReal.useImperativeHandle(ref, () => {
|
||||
const mockRef = (global as any).mockTooltipRef;
|
||||
if (mockRef) {
|
||||
return {
|
||||
forceAlign: mockRef.forceAlign || jest.fn(),
|
||||
get nativeElement() {
|
||||
return mockRef.nativeElement || createMockElement();
|
||||
},
|
||||
get popupElement() {
|
||||
return mockRef.popupElement || createMockDivElement();
|
||||
},
|
||||
} as TooltipRef;
|
||||
}
|
||||
return internalRef.current || {
|
||||
forceAlign: jest.fn(),
|
||||
nativeElement: createMockElement(),
|
||||
popupElement: createMockDivElement(),
|
||||
} as TooltipRef;
|
||||
});
|
||||
|
||||
return <TooltipComponent {...props} ref={internalRef} />;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -224,4 +276,521 @@ describe('Slider', () => {
|
||||
expect(container.querySelector<HTMLDivElement>('.ant-slider-vertical')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ============================= auto adjust placement =============================
|
||||
describe('auto adjust placement', () => {
|
||||
let mockPopupElement: HTMLElement;
|
||||
let mockTriggerElement: HTMLElement;
|
||||
let mockContainer: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create mock elements
|
||||
mockPopupElement = document.createElement('div');
|
||||
mockTriggerElement = document.createElement('div');
|
||||
mockContainer = document.createElement('div');
|
||||
document.body.appendChild(mockContainer);
|
||||
|
||||
// Mock getBoundingClientRect
|
||||
mockPopupElement.getBoundingClientRect = jest.fn(() => ({
|
||||
left: 100,
|
||||
top: 100,
|
||||
right: 200,
|
||||
bottom: 200,
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: 100,
|
||||
y: 100,
|
||||
toJSON: jest.fn(),
|
||||
}));
|
||||
|
||||
mockTriggerElement.getBoundingClientRect = jest.fn(() => ({
|
||||
left: 150,
|
||||
top: 150,
|
||||
right: 150,
|
||||
bottom: 150,
|
||||
width: 0,
|
||||
height: 0,
|
||||
x: 150,
|
||||
y: 150,
|
||||
toJSON: jest.fn(),
|
||||
}));
|
||||
|
||||
mockContainer.getBoundingClientRect = jest.fn(() => ({
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 1000,
|
||||
bottom: 1000,
|
||||
width: 1000,
|
||||
height: 1000,
|
||||
x: 0,
|
||||
y: 0,
|
||||
toJSON: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock window.innerWidth and innerHeight
|
||||
Object.defineProperty(window, 'innerWidth', {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
value: 1000,
|
||||
});
|
||||
Object.defineProperty(window, 'innerHeight', {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
value: 1000,
|
||||
});
|
||||
|
||||
// Set up mock tooltip ref with getters to allow dynamic updates
|
||||
(global as any).mockTooltipRef = {
|
||||
forceAlign: jest.fn(),
|
||||
get nativeElement() {
|
||||
return (global as any).currentMockTriggerElement || mockTriggerElement;
|
||||
},
|
||||
get popupElement() {
|
||||
return (global as any).currentMockPopupElement || mockPopupElement;
|
||||
},
|
||||
};
|
||||
(global as any).currentMockTriggerElement = mockTriggerElement;
|
||||
(global as any).currentMockPopupElement = mockPopupElement;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (document.body.contains(mockContainer)) {
|
||||
document.body.removeChild(mockContainer);
|
||||
}
|
||||
(global as any).mockTooltipRef = null;
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should not adjust placement when tooltip is closed', async () => {
|
||||
const { container } = render(<Slider defaultValue={30} tooltip={{ open: false }} />);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
// When open is false, mergedOpen is false, should not execute detection logic
|
||||
// Since tooltip is not open, cannot get tooltipProps
|
||||
expect(container.querySelector('.ant-slider-handle')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should handle when tooltipRef is null gracefully', async () => {
|
||||
const ref = React.createRef<any>();
|
||||
render(<SliderTooltip title="30" open ref={ref} />);
|
||||
await waitFakeTimer();
|
||||
// Component should render normally without crashing
|
||||
expect(ref.current).toBeDefined();
|
||||
});
|
||||
|
||||
it('should use document.body as container when getPopupContainer is not provided', async () => {
|
||||
const { container } = render(<Slider defaultValue={30} tooltip={{ open: true }} />);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
// Should use document.body as container
|
||||
expect(tooltipProps().placement).toBeDefined();
|
||||
});
|
||||
|
||||
it('should use custom container when getPopupContainer is provided', async () => {
|
||||
const customContainer = document.createElement('div');
|
||||
document.body.appendChild(customContainer);
|
||||
const getPopupContainer = jest.fn(() => customContainer);
|
||||
|
||||
const { container } = render(
|
||||
<Slider defaultValue={30} tooltip={{ open: true, getPopupContainer }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
expect(getPopupContainer).toHaveBeenCalled();
|
||||
document.body.removeChild(customContainer);
|
||||
});
|
||||
|
||||
describe('horizontal mode (placement: top/bottom)', () => {
|
||||
it('should adjust to right when tooltip overflows left with placement top', async () => {
|
||||
// Mock left overflow
|
||||
mockPopupElement.getBoundingClientRect = jest.fn(() => ({
|
||||
left: -50,
|
||||
top: 100,
|
||||
right: 50,
|
||||
bottom: 200,
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: -50,
|
||||
y: 100,
|
||||
toJSON: jest.fn(),
|
||||
}));
|
||||
(global as any).currentMockPopupElement = mockPopupElement;
|
||||
|
||||
const { container } = render(
|
||||
<Slider defaultValue={0} tooltip={{ open: true, placement: 'top' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
await waitFakeTimer(); // Wait for double raf
|
||||
// Should adjust to right
|
||||
expect(tooltipProps().placement).toBe('right');
|
||||
});
|
||||
|
||||
it('should adjust to left when tooltip overflows right with placement top', async () => {
|
||||
// Mock right overflow
|
||||
mockPopupElement.getBoundingClientRect = jest.fn(() => ({
|
||||
left: 950,
|
||||
top: 100,
|
||||
right: 1050,
|
||||
bottom: 200,
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: 950,
|
||||
y: 100,
|
||||
toJSON: jest.fn(),
|
||||
}));
|
||||
(global as any).currentMockPopupElement = mockPopupElement;
|
||||
|
||||
const { container } = render(
|
||||
<Slider defaultValue={100} tooltip={{ open: true, placement: 'top' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
await waitFakeTimer(); // Wait for double raf
|
||||
// Should adjust to left
|
||||
expect(tooltipProps().placement).toBe('left');
|
||||
});
|
||||
|
||||
it('should keep original placement when no overflow with placement top', async () => {
|
||||
// Mock no overflow
|
||||
mockPopupElement.getBoundingClientRect = jest.fn(() => ({
|
||||
left: 100,
|
||||
top: 100,
|
||||
right: 200,
|
||||
bottom: 200,
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: 100,
|
||||
y: 100,
|
||||
toJSON: jest.fn(),
|
||||
}));
|
||||
|
||||
const { container } = render(
|
||||
<Slider defaultValue={50} tooltip={{ open: true, placement: 'top' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
// Should keep original placement
|
||||
expect(tooltipProps().placement).toBe('top');
|
||||
});
|
||||
|
||||
it('should adjust to right when tooltip overflows left with placement bottom', async () => {
|
||||
// Mock left overflow
|
||||
mockPopupElement.getBoundingClientRect = jest.fn(() => ({
|
||||
left: -50,
|
||||
top: 100,
|
||||
right: 50,
|
||||
bottom: 200,
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: -50,
|
||||
y: 100,
|
||||
toJSON: jest.fn(),
|
||||
}));
|
||||
(global as any).currentMockPopupElement = mockPopupElement;
|
||||
|
||||
const { container } = render(
|
||||
<Slider defaultValue={0} tooltip={{ open: true, placement: 'bottom' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
await waitFakeTimer(); // Wait for double raf
|
||||
// Should adjust to right
|
||||
expect(tooltipProps().placement).toBe('right');
|
||||
});
|
||||
|
||||
it('should adjust to left when tooltip overflows right with placement bottom', async () => {
|
||||
// Mock right overflow
|
||||
mockPopupElement.getBoundingClientRect = jest.fn(() => ({
|
||||
left: 950,
|
||||
top: 100,
|
||||
right: 1050,
|
||||
bottom: 200,
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: 950,
|
||||
y: 100,
|
||||
toJSON: jest.fn(),
|
||||
}));
|
||||
(global as any).currentMockPopupElement = mockPopupElement;
|
||||
|
||||
const { container } = render(
|
||||
<Slider defaultValue={100} tooltip={{ open: true, placement: 'bottom' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
await waitFakeTimer(); // Wait for double raf
|
||||
// Should adjust to left
|
||||
expect(tooltipProps().placement).toBe('left');
|
||||
});
|
||||
|
||||
it('should keep original placement when no overflow with placement bottom', async () => {
|
||||
// Mock no overflow
|
||||
mockPopupElement.getBoundingClientRect = jest.fn(() => ({
|
||||
left: 100,
|
||||
top: 100,
|
||||
right: 200,
|
||||
bottom: 200,
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: 100,
|
||||
y: 100,
|
||||
toJSON: jest.fn(),
|
||||
}));
|
||||
|
||||
const { container } = render(
|
||||
<Slider defaultValue={50} tooltip={{ open: true, placement: 'bottom' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
// Should keep original placement
|
||||
expect(tooltipProps().placement).toBe('bottom');
|
||||
});
|
||||
});
|
||||
|
||||
describe('vertical mode (placement: left/right)', () => {
|
||||
it('should adjust to bottom when tooltip overflows top with placement left', async () => {
|
||||
// Mock top overflow
|
||||
mockPopupElement.getBoundingClientRect = jest.fn(() => ({
|
||||
left: 100,
|
||||
top: -50,
|
||||
right: 200,
|
||||
bottom: 50,
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: 100,
|
||||
y: -50,
|
||||
toJSON: jest.fn(),
|
||||
}));
|
||||
(global as any).currentMockPopupElement = mockPopupElement;
|
||||
|
||||
const { container } = render(
|
||||
<Slider vertical defaultValue={100} tooltip={{ open: true, placement: 'left' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
await waitFakeTimer(); // Wait for double raf
|
||||
// Should adjust to bottom
|
||||
expect(tooltipProps().placement).toBe('bottom');
|
||||
});
|
||||
|
||||
it('should adjust to top when tooltip overflows bottom with placement left', async () => {
|
||||
// Mock bottom overflow
|
||||
mockPopupElement.getBoundingClientRect = jest.fn(() => ({
|
||||
left: 100,
|
||||
top: 950,
|
||||
right: 200,
|
||||
bottom: 1050,
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: 100,
|
||||
y: 950,
|
||||
toJSON: jest.fn(),
|
||||
}));
|
||||
(global as any).currentMockPopupElement = mockPopupElement;
|
||||
|
||||
const { container } = render(
|
||||
<Slider vertical defaultValue={0} tooltip={{ open: true, placement: 'left' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
await waitFakeTimer(); // Wait for double raf
|
||||
// Should adjust to top
|
||||
expect(tooltipProps().placement).toBe('top');
|
||||
});
|
||||
|
||||
it('should keep original placement when no overflow with placement left', async () => {
|
||||
// Mock no overflow
|
||||
mockPopupElement.getBoundingClientRect = jest.fn(() => ({
|
||||
left: 100,
|
||||
top: 100,
|
||||
right: 200,
|
||||
bottom: 200,
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: 100,
|
||||
y: 100,
|
||||
toJSON: jest.fn(),
|
||||
}));
|
||||
|
||||
const { container } = render(
|
||||
<Slider vertical defaultValue={50} tooltip={{ open: true, placement: 'left' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
// Should keep original placement
|
||||
expect(tooltipProps().placement).toBe('left');
|
||||
});
|
||||
|
||||
it('should adjust to bottom when tooltip overflows top with placement right', async () => {
|
||||
// Mock top overflow
|
||||
mockPopupElement.getBoundingClientRect = jest.fn(() => ({
|
||||
left: 100,
|
||||
top: -50,
|
||||
right: 200,
|
||||
bottom: 50,
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: 100,
|
||||
y: -50,
|
||||
toJSON: jest.fn(),
|
||||
}));
|
||||
(global as any).currentMockPopupElement = mockPopupElement;
|
||||
|
||||
const { container } = render(
|
||||
<Slider vertical defaultValue={100} tooltip={{ open: true, placement: 'right' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
await waitFakeTimer(); // Wait for double raf
|
||||
// Should adjust to bottom
|
||||
expect(tooltipProps().placement).toBe('bottom');
|
||||
});
|
||||
|
||||
it('should adjust to top when tooltip overflows bottom with placement right', async () => {
|
||||
// Mock bottom overflow
|
||||
mockPopupElement.getBoundingClientRect = jest.fn(() => ({
|
||||
left: 100,
|
||||
top: 950,
|
||||
right: 200,
|
||||
bottom: 1050,
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: 100,
|
||||
y: 950,
|
||||
toJSON: jest.fn(),
|
||||
}));
|
||||
(global as any).currentMockPopupElement = mockPopupElement;
|
||||
|
||||
const { container } = render(
|
||||
<Slider vertical defaultValue={0} tooltip={{ open: true, placement: 'right' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
await waitFakeTimer(); // Wait for double raf
|
||||
// Should adjust to top
|
||||
expect(tooltipProps().placement).toBe('top');
|
||||
});
|
||||
|
||||
it('should keep original placement when no overflow with placement right', async () => {
|
||||
// Mock no overflow
|
||||
mockPopupElement.getBoundingClientRect = jest.fn(() => ({
|
||||
left: 100,
|
||||
top: 100,
|
||||
right: 200,
|
||||
bottom: 200,
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: 100,
|
||||
y: 100,
|
||||
toJSON: jest.fn(),
|
||||
}));
|
||||
|
||||
const { container } = render(
|
||||
<Slider vertical defaultValue={50} tooltip={{ open: true, placement: 'right' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
// Should keep original placement
|
||||
expect(tooltipProps().placement).toBe('right');
|
||||
});
|
||||
});
|
||||
|
||||
it('should reset placement when tooltip is closed', async () => {
|
||||
const { container, rerender } = render(
|
||||
<Slider defaultValue={30} tooltip={{ open: true, placement: 'top' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
// Close tooltip
|
||||
rerender(<Slider defaultValue={30} tooltip={{ open: false, placement: 'top' }} />);
|
||||
await waitFakeTimer();
|
||||
// Placement should be reset
|
||||
expect(tooltipProps().placement).toBe('top');
|
||||
});
|
||||
|
||||
it('should adjust placement when value changes', async () => {
|
||||
const { container, rerender } = render(
|
||||
<Slider value={0} tooltip={{ open: true, placement: 'top' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
// Change value
|
||||
rerender(<Slider value={100} tooltip={{ open: true, placement: 'top' }} />);
|
||||
await waitFakeTimer();
|
||||
// Should re-detect and adjust
|
||||
expect(tooltipProps().placement).toBeDefined();
|
||||
});
|
||||
|
||||
it('should adjust placement when title changes', async () => {
|
||||
const { container, rerender } = render(
|
||||
<Slider defaultValue={30} tooltip={{ open: true, placement: 'top' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
// Change title (via formatter)
|
||||
rerender(
|
||||
<Slider
|
||||
defaultValue={30}
|
||||
tooltip={{ open: true, placement: 'top', formatter: (val) => `New ${val}` }}
|
||||
/>,
|
||||
);
|
||||
await waitFakeTimer();
|
||||
// Should re-detect and adjust
|
||||
expect(tooltipProps().placement).toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle resize event', async () => {
|
||||
const { container } = render(
|
||||
<Slider defaultValue={30} tooltip={{ open: true, placement: 'top' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
// Trigger resize event
|
||||
act(() => {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
jest.runAllTimers();
|
||||
});
|
||||
await waitFakeTimer();
|
||||
// Should re-detect and adjust
|
||||
expect(tooltipProps().placement).toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle scroll event', async () => {
|
||||
const { container } = render(
|
||||
<Slider defaultValue={30} tooltip={{ open: true, placement: 'top' }} />,
|
||||
);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
// Trigger scroll event
|
||||
act(() => {
|
||||
window.dispatchEvent(new Event('scroll'));
|
||||
jest.runAllTimers();
|
||||
});
|
||||
await waitFakeTimer();
|
||||
// Should re-detect and adjust
|
||||
expect(tooltipProps().placement).toBeDefined();
|
||||
});
|
||||
|
||||
it('should use default placement top when placement is undefined', async () => {
|
||||
const { container } = render(<Slider defaultValue={30} tooltip={{ open: true }} />);
|
||||
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||
await waitFakeTimer();
|
||||
// Should use default top
|
||||
expect(tooltipProps().placement).toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle draggingDelete prop', async () => {
|
||||
render(<SliderTooltip title="30" open draggingDelete placement="top" />);
|
||||
await waitFakeTimer();
|
||||
// When draggingDelete is true, tooltip should not be displayed
|
||||
expect(tooltipProps().open).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user