import {act, cleanup, fireEvent, render, screen} from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type {ReactNode} from 'react'; import {useState} from 'react'; import { FloatingFocusManager, FloatingNode, FloatingPortal, FloatingTree, useDismiss, useFloating, useFloatingNodeId, useFloatingParentNodeId, useFocus, useInteractions, } from '../../src'; import type {UseDismissProps} from '../../src/hooks/useDismiss'; import {normalizeProp} from '../../src/hooks/useDismiss'; function App(props: UseDismissProps) { const [open, setOpen] = useState(true); const {refs, context} = useFloating({ open, onOpenChange(open, _, reason) { setOpen(open); if (props.outsidePress) { expect(reason).toBe('outside-press'); } else if (props.escapeKey) { expect(reason).toBe('escape-key'); } else if (props.referencePress) { expect(reason).toBe('reference-press'); } else if (props.ancestorScroll) { expect(reason).toBe('ancestor-scroll'); } }, }); const {getReferenceProps, getFloatingProps} = useInteractions([ useDismiss(context, props), ]); return ( <> , ); expect(screen.queryByTestId('outer')).toBeInTheDocument(); expect(screen.queryByTestId('inner')).toBeInTheDocument(); fireEvent.pointerDown(document.body); expect(screen.queryByTestId('outer')).not.toBeInTheDocument(); expect(screen.queryByTestId('inner')).not.toBeInTheDocument(); cleanup(); }); test('false', async () => { render( , ); expect(screen.queryByTestId('outer')).toBeInTheDocument(); expect(screen.queryByTestId('inner')).toBeInTheDocument(); fireEvent.pointerDown(document.body); expect(screen.queryByTestId('outer')).toBeInTheDocument(); expect(screen.queryByTestId('inner')).not.toBeInTheDocument(); fireEvent.pointerDown(document.body); expect(screen.queryByTestId('outer')).not.toBeInTheDocument(); expect(screen.queryByTestId('inner')).not.toBeInTheDocument(); cleanup(); }); test('mixed', async () => { render( , ); expect(screen.queryByTestId('outer')).toBeInTheDocument(); expect(screen.queryByTestId('inner')).toBeInTheDocument(); fireEvent.pointerDown(document.body); expect(screen.queryByTestId('outer')).toBeInTheDocument(); expect(screen.queryByTestId('inner')).not.toBeInTheDocument(); fireEvent.pointerDown(document.body); expect(screen.queryByTestId('outer')).not.toBeInTheDocument(); expect(screen.queryByTestId('inner')).not.toBeInTheDocument(); cleanup(); }); }); describe('escapeKey', () => { test('without FloatingTree', async () => { function App() { const [popoverOpen, setPopoverOpen] = useState(true); const [tooltipOpen, setTooltipOpen] = useState(false); const popover = useFloating({ open: popoverOpen, onOpenChange: setPopoverOpen, }); const tooltip = useFloating({ open: tooltipOpen, onOpenChange: setTooltipOpen, }); const popoverInteractions = useInteractions([ useDismiss(popover.context), ]); const tooltipInteractions = useInteractions([ useFocus(tooltip.context, {visibleOnly: false}), useDismiss(tooltip.context), ]); return ( <> , ); expect(screen.queryByTestId('outer')).toBeInTheDocument(); expect(screen.queryByTestId('inner')).toBeInTheDocument(); await userEvent.keyboard('{Escape}'); expect(screen.queryByTestId('outer')).not.toBeInTheDocument(); expect(screen.queryByTestId('inner')).not.toBeInTheDocument(); cleanup(); }); test('false', async () => { render( , ); expect(screen.queryByTestId('outer')).toBeInTheDocument(); expect(screen.queryByTestId('inner')).toBeInTheDocument(); await userEvent.keyboard('{Escape}'); expect(screen.queryByTestId('outer')).toBeInTheDocument(); expect(screen.queryByTestId('inner')).not.toBeInTheDocument(); await userEvent.keyboard('{Escape}'); expect(screen.queryByTestId('outer')).not.toBeInTheDocument(); expect(screen.queryByTestId('inner')).not.toBeInTheDocument(); cleanup(); }); test('mixed', async () => { render( , ); expect(screen.queryByTestId('outer')).toBeInTheDocument(); expect(screen.queryByTestId('inner')).toBeInTheDocument(); await userEvent.keyboard('{Escape}'); expect(screen.queryByTestId('outer')).toBeInTheDocument(); expect(screen.queryByTestId('inner')).not.toBeInTheDocument(); await userEvent.keyboard('{Escape}'); expect(screen.queryByTestId('outer')).not.toBeInTheDocument(); expect(screen.queryByTestId('inner')).not.toBeInTheDocument(); cleanup(); }); }); }); describe('capture', () => { describe('prop resolution', () => { test('undefined', () => { const {escapeKey: escapeKeyCapture, outsidePress: outsidePressCapture} = normalizeProp(); expect(escapeKeyCapture).toBe(false); expect(outsidePressCapture).toBe(true); }); test('{}', () => { const {escapeKey: escapeKeyCapture, outsidePress: outsidePressCapture} = normalizeProp({}); expect(escapeKeyCapture).toBe(false); expect(outsidePressCapture).toBe(true); }); test('true', () => { const {escapeKey: escapeKeyCapture, outsidePress: outsidePressCapture} = normalizeProp(true); expect(escapeKeyCapture).toBe(true); expect(outsidePressCapture).toBe(true); }); test('false', () => { const {escapeKey: escapeKeyCapture, outsidePress: outsidePressCapture} = normalizeProp(false); expect(escapeKeyCapture).toBe(false); expect(outsidePressCapture).toBe(false); }); test('{ escapeKey: true }', () => { const {escapeKey: escapeKeyCapture, outsidePress: outsidePressCapture} = normalizeProp({ escapeKey: true, }); expect(escapeKeyCapture).toBe(true); expect(outsidePressCapture).toBe(true); }); test('{ outsidePress: false }', () => { const {escapeKey: escapeKeyCapture, outsidePress: outsidePressCapture} = normalizeProp({ outsidePress: false, }); expect(escapeKeyCapture).toBe(false); expect(outsidePressCapture).toBe(false); }); }); const Overlay = ({children}: {children: ReactNode}) => { return (
e.stopPropagation()} onKeyDown={(e) => { if (e.key === 'Escape') { e.stopPropagation(); } }} > outside {children}
); }; const Dialog = ({ id, children, ...props }: UseDismissProps & {id: string; children: ReactNode}) => { const [open, setOpen] = useState(true); const nodeId = useFloatingNodeId(); const {refs, context} = useFloating({ open, onOpenChange: setOpen, nodeId, }); const {getReferenceProps, getFloatingProps} = useInteractions([ useDismiss(context, props), ]); return (