|
| 1 | +import { fireEvent, render, screen } from "@testing-library/react"; |
| 2 | +import { type PropsWithChildren, type ReactElement } from "react"; |
| 3 | +import { ThemeProvider } from "styled-components"; |
| 4 | +import { theme } from "@web/common/styles/theme"; |
| 5 | +import { afterEach, describe, expect, it, mock } from "bun:test"; |
| 6 | + |
| 7 | +let listNavigationProps: { focusItemOnHover?: boolean } | null = null; |
| 8 | +let floatingOptions: { onOpenChange?: (open: boolean) => void } | null = null; |
| 9 | +let focusManagerProps: { closeOnFocusOut?: boolean } | null = null; |
| 10 | + |
| 11 | +mock.module("@floating-ui/react", () => ({ |
| 12 | + autoUpdate: mock(), |
| 13 | + flip: mock(() => ({})), |
| 14 | + FloatingFocusManager: ({ |
| 15 | + children, |
| 16 | + ...props |
| 17 | + }: PropsWithChildren<{ closeOnFocusOut?: boolean }>) => { |
| 18 | + focusManagerProps = props; |
| 19 | + return <>{children}</>; |
| 20 | + }, |
| 21 | + FloatingPortal: ({ children }: PropsWithChildren) => <>{children}</>, |
| 22 | + offset: mock(() => ({})), |
| 23 | + shift: mock(() => ({})), |
| 24 | + useClick: mock(() => ({})), |
| 25 | + useDismiss: mock(() => ({})), |
| 26 | + useFloating: (options: { onOpenChange?: (open: boolean) => void }) => { |
| 27 | + floatingOptions = options; |
| 28 | + return { |
| 29 | + context: { |
| 30 | + floatingStyles: {}, |
| 31 | + }, |
| 32 | + refs: { |
| 33 | + setFloating: mock(), |
| 34 | + setReference: mock(), |
| 35 | + }, |
| 36 | + }; |
| 37 | + }, |
| 38 | + useInteractions: mock(() => ({ |
| 39 | + getFloatingProps: (props = {}) => props, |
| 40 | + getItemProps: (props = {}) => props, |
| 41 | + getReferenceProps: ( |
| 42 | + props: { onClick?: (event: MouseEvent) => void } = {}, |
| 43 | + ) => ({ |
| 44 | + ...props, |
| 45 | + onClick: (event: MouseEvent) => { |
| 46 | + props.onClick?.(event); |
| 47 | + floatingOptions?.onOpenChange?.(true); |
| 48 | + }, |
| 49 | + }), |
| 50 | + })), |
| 51 | + useListNavigation: mock((_context, props) => { |
| 52 | + listNavigationProps = props as { focusItemOnHover?: boolean }; |
| 53 | + return {}; |
| 54 | + }), |
| 55 | + useRole: mock(() => ({})), |
| 56 | +})); |
| 57 | + |
| 58 | +mock.module("@web/common/hooks/useGridMaxZIndex", () => ({ |
| 59 | + useGridMaxZIndex: () => 0, |
| 60 | +})); |
| 61 | + |
| 62 | +const { ActionsMenu } = |
| 63 | + require("./ActionsMenu") as typeof import("./ActionsMenu"); |
| 64 | + |
| 65 | +const renderWithTheme = (ui: ReactElement) => |
| 66 | + render(<ThemeProvider theme={theme}>{ui}</ThemeProvider>); |
| 67 | + |
| 68 | +describe("ActionsMenu", () => { |
| 69 | + afterEach(() => { |
| 70 | + floatingOptions = null; |
| 71 | + focusManagerProps = null; |
| 72 | + listNavigationProps = null; |
| 73 | + }); |
| 74 | + |
| 75 | + it("keeps hover from moving focus to menu items", () => { |
| 76 | + renderWithTheme(<ActionsMenu bgColor="#fff">{() => null}</ActionsMenu>); |
| 77 | + |
| 78 | + expect(listNavigationProps?.focusItemOnHover).toBe(false); |
| 79 | + }); |
| 80 | + |
| 81 | + it("keeps the floating menu from dismissing on focus movement", () => { |
| 82 | + renderWithTheme(<ActionsMenu bgColor="#fff">{() => null}</ActionsMenu>); |
| 83 | + |
| 84 | + fireEvent.click(screen.getByRole("button", { name: "Open actions menu" })); |
| 85 | + |
| 86 | + expect(focusManagerProps?.closeOnFocusOut).toBe(false); |
| 87 | + }); |
| 88 | +}); |
0 commit comments