diff --git a/packages/blade/src/components/Popover/__tests__/Popover.test.stories.tsx b/packages/blade/src/components/Popover/__tests__/Popover.test.stories.tsx new file mode 100644 index 00000000000..82af3a52cc8 --- /dev/null +++ b/packages/blade/src/components/Popover/__tests__/Popover.test.stories.tsx @@ -0,0 +1,271 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable import/no-extraneous-dependencies */ +import type { StoryFn } from '@storybook/react'; +import { within, userEvent } from '@storybook/testing-library'; +import { expect, jest } from '@storybook/jest'; +import React from 'react'; +import type { PopoverProps, PopoverTriggerProps } from '..'; +import { PopoverInteractiveWrapper, Popover as PopoverComponent } from '..'; +import { Button } from '~components/Button'; +import { Text } from '~components/Typography'; +import { Box } from '~components/Box'; +import { Badge } from '~components/Badge'; +import BaseBox from '~components/Box/BaseBox'; + +const onOpenChange = jest.fn(); + +const PopoverExample = (props: PopoverProps): React.ReactElement => { + return ( + + + + ); +}; + +const sleep = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); + +export const TestPopoverOpenClose: StoryFn = ( + props, +): React.ReactElement => { + onOpenChange.mockReset(); + return ; +}; + +TestPopoverOpenClose.args = { + title: 'Hello World', + content: Some text, +}; +TestPopoverOpenClose.play = async () => { + const { getByRole, queryByText } = within(document.body); + await expect(queryByText('Hello World')).not.toBeInTheDocument(); + const showButton = getByRole('button', { name: 'Show Popover' }); + // open + await userEvent.click(showButton); + await sleep(400); + await expect(onOpenChange).toBeCalledWith({ isOpen: true }); + await expect(queryByText('Hello World')).toBeVisible(); + // close + const closeButton = getByRole('button', { name: 'Close' }); + await expect(closeButton).toHaveFocus(); + await userEvent.click(closeButton); + await sleep(400); + await expect(onOpenChange).toBeCalledWith({ isOpen: false }); + await expect(queryByText('Hello World')).not.toBeInTheDocument(); + await expect(showButton).toHaveFocus(); + await expect(onOpenChange).toBeCalledTimes(2); +}; + +// Controlled +export const TestPopoverControlled: StoryFn = (): React.ReactElement => { + const [isOpen, setIsOpen] = React.useState(true); + const toggle = () => { + setIsOpen((prev) => !prev); + }; + return ( + + + Hello World + + + } + isOpen={isOpen} + onOpenChange={({ isOpen }) => setIsOpen(isOpen)} + > + + + + + ); +}; + +TestPopoverControlled.play = async () => { + const { getByRole, queryByText } = within(document.body); + const popoverContent = 'Hello World'; + const toggleButton = getByRole('button', { name: 'Controlled Show', hidden: true }); + const internalButton = getByRole('button', { name: 'Internal Click', hidden: true }); + + await sleep(1000); + // open by default + await expect(queryByText(popoverContent)).toBeVisible(); + // close + await userEvent.click(internalButton); + await sleep(1000); + await expect(queryByText(popoverContent)).not.toBeInTheDocument(); + // open again + await userEvent.click(toggleButton); + await sleep(400); + await expect(queryByText(popoverContent)).toBeVisible(); + // close via close button + const closeButton = getByRole('button', { name: 'Close' }); + await expect(closeButton).toHaveFocus(); + await userEvent.click(closeButton); + await sleep(400); + await expect(queryByText(popoverContent)).not.toBeInTheDocument(); + await expect(toggleButton).toHaveFocus(); +}; + +// Uncontrolled +export const TestPopoverUncontrolled: StoryFn = (): React.ReactElement => { + return ( + + Hello World} + defaultIsOpen={true} + onOpenChange={onOpenChange} + > + + + + ); +}; + +TestPopoverUncontrolled.play = async () => { + onOpenChange.mockReset(); + const { getByRole, queryByText } = within(document.body); + const popoverContent = 'Hello World'; + const toggleButton = getByRole('button', { name: 'Show Popover', hidden: true }); + + await expect(onOpenChange).not.toBeCalled(); + await sleep(600); + // open by default + await expect(queryByText(popoverContent)).toBeVisible(); + // close + await userEvent.click(toggleButton); + await sleep(400); + await expect(queryByText(popoverContent)).not.toBeInTheDocument(); + await expect(onOpenChange).toBeCalledWith({ isOpen: false }); + // open + await userEvent.click(toggleButton); + await sleep(400); + await expect(queryByText(popoverContent)).toBeVisible(); + await expect(onOpenChange).toBeCalledWith({ isOpen: true }); + await expect(onOpenChange).toBeCalledTimes(2); +}; + +// PopoverInteractiveWrapper +export const TestPopoverInteractiveWrapper: StoryFn< + typeof PopoverComponent +> = (): React.ReactElement => { + onOpenChange.mockReset(); + return ( + New Badge Content} onOpenChange={onOpenChange}> + + NEW + + + ); +}; + +TestPopoverInteractiveWrapper.play = async () => { + const { getByRole, queryByText } = within(document.body); + const popoverContent = 'New Badge Content'; + await expect(queryByText(popoverContent)).not.toBeInTheDocument(); + const badge = getByRole('button', { name: 'NEW' }); + // open + await userEvent.click(badge); + await sleep(400); + await expect(onOpenChange).toBeCalledWith({ isOpen: true }); + await expect(queryByText(popoverContent)).toBeVisible(); + // close + const closeButton = getByRole('button', { name: 'Close' }); + await expect(closeButton).toHaveFocus(); + await userEvent.click(closeButton); + await sleep(400); + await expect(onOpenChange).toBeCalledWith({ isOpen: false }); + await expect(queryByText(popoverContent)).not.toBeInTheDocument(); + await expect(badge).toHaveFocus(); + await expect(onOpenChange).toBeCalledTimes(2); +}; + +// Custom Trigger +const MyCustomTriggerButton = React.forwardRef< + HTMLDivElement, + { children: string } & PopoverTriggerProps +>(({ children, onTouchEnd, ...props }, ref) => { + return ( + // just spread the props + + {children} + + ); +}); + +export const TestCustomTrigger: StoryFn = (): React.ReactElement => { + onOpenChange.mockReset(); + return ( + Hello Custom Trigger} onOpenChange={onOpenChange}> + Show Popover + + ); +}; +TestCustomTrigger.play = async () => { + const { getByRole, queryByText } = within(document.body); + const popoverContent = 'Hello Custom Trigger'; + await expect(queryByText(popoverContent)).not.toBeInTheDocument(); + const badge = getByRole('button', { name: 'Show Popover' }); + // open + await userEvent.click(badge); + await sleep(400); + await expect(onOpenChange).toBeCalledWith({ isOpen: true }); + await expect(queryByText(popoverContent)).toBeVisible(); + // close + const closeButton = getByRole('button', { name: 'Close' }); + await expect(closeButton).toHaveFocus(); + await userEvent.click(closeButton); + await sleep(400); + await expect(onOpenChange).toBeCalledWith({ isOpen: false }); + await expect(queryByText(popoverContent)).not.toBeInTheDocument(); + await expect(badge).toHaveFocus(); + await expect(onOpenChange).toBeCalledTimes(2); +}; + +export const TestInitialFocus: StoryFn = (): React.ReactElement => { + onOpenChange.mockReset(); + const initialRef = React.useRef(null); + return ( + + Hello Initial Focus + + + } + > + + + ); +}; +TestInitialFocus.play = async () => { + const { getByRole, queryByText } = within(document.body); + const popoverContent = 'Hello Initial Focus'; + await sleep(600); + await expect(queryByText(popoverContent)).toBeVisible(); + const focusedButton = getByRole('button', { name: 'Focus on me' }); + await expect(focusedButton).toHaveFocus(); +}; + +export default { + title: 'Components/Interaction Tests/Popover', + component: PopoverComponent, + parameters: { + controls: { + disable: true, + }, + a11y: { disable: true }, + essentials: { disable: true }, + actions: { disable: true }, + }, +};