diff --git a/src/@next/Tooltip/Tooltip.test.tsx b/src/@next/Tooltip/Tooltip.test.tsx
new file mode 100644
index 000000000..9379214ae
--- /dev/null
+++ b/src/@next/Tooltip/Tooltip.test.tsx
@@ -0,0 +1,69 @@
+import * as React from 'react';
+import '@testing-library/jest-dom/extend-expect';
+import { act, fireEvent, render } from '@testing-library/react';
+
+import { Tooltip } from './Tooltip';
+
+beforeAll(() => {
+ Object.defineProperty(global, 'DOMRect', {
+ configurable: true,
+ value: class {
+ bottom: number;
+ height: number;
+ left: number;
+ right: number;
+ top: number;
+ width: number;
+ x: number;
+ y: number;
+
+ constructor(x = 0, y = 0, width = 0, height = 0) {
+ this.bottom = y + height;
+ this.height = height;
+ this.left = x;
+ this.right = x + width;
+ this.top = y;
+ this.width = width;
+ this.x = x;
+ this.y = y;
+ }
+ },
+ });
+});
+
+beforeEach(() => {
+ jest.useFakeTimers();
+ const portalContainer = document.createElement('div');
+ portalContainer.id = 'glints-portal-container';
+ document.body.appendChild(portalContainer);
+ jest
+ .spyOn(Element.prototype, 'getClientRects')
+ .mockReturnValue([new DOMRect(0, 0, 20, 20)] as unknown as DOMRectList);
+});
+
+afterEach(() => {
+ jest.useRealTimers();
+ jest.restoreAllMocks();
+ document.getElementById('glints-portal-container')?.remove();
+});
+
+describe('', () => {
+ test('stays open after a rapid leave and re-entry', () => {
+ const { getByText, queryByText } = render(
+
+ mock content
+
+ );
+ const trigger = getByText('mock content');
+
+ fireEvent.mouseEnter(trigger);
+ fireEvent.mouseLeave(trigger);
+ fireEvent.mouseEnter(trigger);
+
+ act(() => {
+ jest.advanceTimersByTime(370);
+ });
+
+ expect(queryByText('mock text')).toBeInTheDocument();
+ });
+});
diff --git a/src/@next/Tooltip/Tooltip.tsx b/src/@next/Tooltip/Tooltip.tsx
index c63a1b989..8fa0b1987 100644
--- a/src/@next/Tooltip/Tooltip.tsx
+++ b/src/@next/Tooltip/Tooltip.tsx
@@ -254,8 +254,25 @@ export const Tooltip = ({
content
);
+ const timeoutRef = useRef(null);
+ const [animate, setAnimate] = useState(false);
+
+ useEffect(
+ () => () => {
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
+ },
+ []
+ );
+
const handleMouseEnter = () => {
- if (!clickable) setIsActive(true);
+ if (!clickable) {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ timeoutRef.current = null;
+ }
+ setAnimate(false);
+ setIsActive(true);
+ }
};
const handleMouseLeave = () => {
if (!clickable) {
@@ -270,8 +287,6 @@ export const Tooltip = ({
}
};
- const timeoutRef = useRef(null);
- const [animate, setAnimate] = useState(false);
const handleAnimation = () => {
// if you click during the tooltip's lifespan, it should reset the timeout
if (timeoutRef.current) {