diff --git a/packages/blade/src/components/Tooltip/Tooltip.web.tsx b/packages/blade/src/components/Tooltip/Tooltip.web.tsx index 34e443f91c8..ac610b01c9c 100644 --- a/packages/blade/src/components/Tooltip/Tooltip.web.tsx +++ b/packages/blade/src/components/Tooltip/Tooltip.web.tsx @@ -53,13 +53,8 @@ const Tooltip = ({ open: isOpen, strategy: 'fixed', onOpenChange: (open) => { - if (open) { - setIsOpen(true); - onOpenChange?.({ isOpen: open }); - } else { - setIsOpen(false); - onOpenChange?.({ isOpen: open }); - } + setIsOpen(open); + onOpenChange?.({ isOpen: open }); }, middleware: [ shift({ crossAxis: false, padding: GAP }), diff --git a/packages/blade/src/components/Tooltip/__tests__/Tooltip.test.stories.tsx b/packages/blade/src/components/Tooltip/__tests__/Tooltip.test.stories.tsx new file mode 100644 index 00000000000..8d3c0dbebdc --- /dev/null +++ b/packages/blade/src/components/Tooltip/__tests__/Tooltip.test.stories.tsx @@ -0,0 +1,186 @@ +import type { StoryFn } from '@storybook/react'; +import { within, userEvent } from '@storybook/testing-library'; +import { expect, jest } from '@storybook/jest'; +import React from 'react'; +import { TooltipInteractiveWrapper, Tooltip as TooltipComponent } from '..'; +import { Button } from '~components/Button'; +import { Text } from '~components/Typography'; +import { Badge } from '~components/Badge'; +import BaseBox from '~components/Box/BaseBox'; +import type { BladeCommonEvents } from '~components/types'; + +const sleep = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); + +const onOpenChange = jest.fn(); + +export const TestTooltipOpenClose: StoryFn = ( + props, +): React.ReactElement => { + return ( + + + + ); +}; + +TestTooltipOpenClose.args = { + title: 'Hello World', + content: 'Some text', +}; +TestTooltipOpenClose.play = async () => { + const { getByRole, queryByText } = within(document.body); + await expect(queryByText('Hello World')).not.toBeInTheDocument(); + const showButton = getByRole('button', { name: 'Hover me' }); + // open + await userEvent.hover(showButton); + await sleep(400); + await expect(onOpenChange).toBeCalledWith({ isOpen: true }); + await expect(queryByText('Hello World')).toBeVisible(); + // close + await userEvent.unhover(showButton); + await sleep(500); + await expect(onOpenChange).toBeCalledWith({ isOpen: false }); + await expect(queryByText('Hello World')).not.toBeInTheDocument(); +}; + +// TooltipInteractiveWrapper +export const TestTooltipInteractiveWrapper: StoryFn = ( + props, +): React.ReactElement => { + onOpenChange.mockReset(); + return ( + + + NEW + + + ); +}; +TestTooltipInteractiveWrapper.args = { + content: 'Hello World', +}; +TestTooltipInteractiveWrapper.play = async () => { + const { getByText, queryByText } = within(document.body); + const tooltipContent = 'Hello World'; + await expect(queryByText(tooltipContent)).not.toBeInTheDocument(); + const badge = getByText('NEW'); + // open + await userEvent.hover(badge); + await sleep(400); + await expect(onOpenChange).toBeCalledWith({ isOpen: true }); + await expect(queryByText(tooltipContent)).toBeVisible(); + // close + await userEvent.unhover(badge); + await sleep(500); + await expect(onOpenChange).toBeCalledWith({ isOpen: false }); + await expect(queryByText(tooltipContent)).not.toBeInTheDocument(); +}; + +const CustomTrigger = React.forwardRef< + HTMLDivElement, + { children: React.ReactNode } & BladeCommonEvents +>( + ( + { + children, + onBlur, + onFocus, + onMouseLeave, + onMouseMove, + onPointerDown, + onPointerEnter, + onTouchEnd, + onTouchStart, + }, + ref, + ) => { + return ( + + {children} + + ); + }, +); + +// custom trigger +export const TestCustomTrigger: StoryFn = (props): React.ReactElement => { + onOpenChange.mockReset(); + return ( + + Custom Trigger + + ); +}; +TestCustomTrigger.args = { + content: 'Hello World', +}; +TestCustomTrigger.play = async () => { + const { getByText, queryByText } = within(document.body); + const tooltipContent = 'Hello World'; + await expect(queryByText(tooltipContent)).not.toBeInTheDocument(); + const trigger = getByText('Custom Trigger'); + // open + await userEvent.hover(trigger); + await sleep(400); + await expect(onOpenChange).toBeCalledWith({ isOpen: true }); + await expect(queryByText(tooltipContent)).toBeVisible(); + // close + await userEvent.unhover(trigger); + await sleep(500); + await expect(onOpenChange).toBeCalledWith({ isOpen: false }); + await expect(queryByText(tooltipContent)).not.toBeInTheDocument(); +}; + +// should open/close immediately on focus/blur without the default delay of 300ms +export const TestTooltipOpenCloseFocus: StoryFn = ( + props, +): React.ReactElement => { + return ( + + + + ); +}; + +TestTooltipOpenCloseFocus.args = { + title: 'Hello World', + content: 'Some text', +}; +TestTooltipOpenCloseFocus.play = async () => { + const { queryByText } = within(document.body); + await expect(queryByText('Hello World')).not.toBeInTheDocument(); + // open + await userEvent.keyboard('{Tab}', {}); + await sleep(200); + await expect(onOpenChange).toBeCalledWith({ isOpen: true }); + await expect(queryByText('Hello World')).toBeVisible(); + // close + await userEvent.keyboard('{Tab}', {}); + await sleep(300); + await expect(onOpenChange).toBeCalledWith({ isOpen: false }); + await expect(queryByText('Hello World')).not.toBeInTheDocument(); +}; + +export default { + title: 'Components/Interaction Tests/Tooltip', + component: TooltipComponent, + parameters: { + controls: { + disable: true, + }, + a11y: { disable: true }, + essentials: { disable: true }, + actions: { disable: true }, + }, +};