import React from 'react' import { usePicassoRoot } from '@toptal/picasso-provider' import { act, render, screen } from '@testing-library/react' import type { PopperHandle, Props } from './Popper' import Popper from './Popper' jest.mock('@toptal/picasso-provider', () => ({ useBreakpoint: () => true, usePicassoRoot: jest.fn(), })) jest.mock('@toptal/picasso-utils', () => ({ __esModule: true, useWidthOf: () => '300px', })) const mockedUsePicassoRoot = usePicassoRoot as jest.Mock< ReturnType > const rootDiv = document.createElement('div') rootDiv.setAttribute('id', 'root') const children = 'some children' // position is computed in a microtask after render const flushPosition = async () => { await act(async () => {}) } const renderPopper = (props: Partial = {}) => { return render( {children} ) } describe('Popper', () => { beforeEach(() => { document.body.appendChild(rootDiv) mockedUsePicassoRoot.mockReturnValue(rootDiv) }) afterEach(() => { rootDiv.remove() }) it('renders children inside the default picasso root container', async () => { renderPopper() await flushPosition() const popper = screen.getByRole('tooltip') expect(popper).toHaveTextContent(children) expect(rootDiv).toContainElement(popper) }) it('renders children inside a passed container', async () => { const container = document.createElement('div') document.body.appendChild(container) renderPopper({ container }) await flushPosition() expect(container).toContainElement(screen.getByRole('tooltip')) container.remove() }) it('does not render children when closed', async () => { renderPopper({ open: false }) await flushPosition() expect(screen.queryByRole('tooltip')).not.toBeInTheDocument() }) it('keeps children mounted but hidden when closed with keepMounted', async () => { renderPopper({ open: false, keepMounted: true }) await flushPosition() expect(screen.getByRole('tooltip', { hidden: true })).toHaveStyle({ display: 'none', }) }) it('applies the anchor element width when autoWidth is set', async () => { renderPopper() await flushPosition() expect(screen.getByRole('tooltip')).toHaveStyle({ width: '300px' }) }) it('applies a custom width over the anchor element width', async () => { renderPopper({ width: '400px' }) await flushPosition() expect(screen.getByRole('tooltip')).toHaveStyle({ width: '400px' }) }) it('does not assign a width when autoWidth is disabled', async () => { renderPopper({ width: undefined, autoWidth: false }) await flushPosition() expect(screen.getByRole('tooltip').style.width).toBe('') }) it('exposes a popper.js-compatible handle via ref', async () => { const ref = React.createRef() renderPopper({ ref } as Partial) await flushPosition() expect(ref.current?.popper).toBe(screen.getByRole('tooltip')) expect(typeof ref.current?.update).toBe('function') expect(typeof ref.current?.scheduleUpdate).toBe('function') }) it('calls onCreate once positioned', async () => { const onCreate = jest.fn() renderPopper({ popperOptions: { onCreate } }) await flushPosition() expect(onCreate).toHaveBeenCalledTimes(1) }) it('renders in place when portal is disabled', async () => { const { container } = renderPopper({ disablePortal: true }) await flushPosition() expect(container).toContainElement(screen.getByRole('tooltip')) }) it('positions with absolute strategy by default', async () => { renderPopper() await flushPosition() expect(screen.getByRole('tooltip')).toHaveStyle({ position: 'absolute' }) }) it('positions with fixed strategy via popperOptions.positionFixed', async () => { renderPopper({ popperOptions: { positionFixed: true } }) await flushPosition() expect(screen.getByRole('tooltip')).toHaveStyle({ position: 'fixed' }) }) describe('role', () => { it('defaults the floating element to role="tooltip"', async () => { renderPopper() await flushPosition() expect(screen.getByRole('tooltip')).toHaveTextContent(children) }) it('applies a custom role over the default', async () => { renderPopper({ role: 'dialog' }) await flushPosition() const popper = screen.getByRole('dialog') expect(popper).toHaveTextContent(children) expect(screen.queryByRole('tooltip')).not.toBeInTheDocument() }) it('marks the floating element presentational with role="presentation"', async () => { renderPopper({ role: 'presentation' }) await flushPosition() // `presentation` removes it from the a11y tree — query by content expect(screen.getByText(children)).toBeInTheDocument() expect(screen.queryByRole('tooltip')).not.toBeInTheDocument() }) it('always marks the floating element with data-picasso-popper regardless of role', async () => { renderPopper({ role: 'presentation' }) await flushPosition() // Modal's focus trap keys on this marker (roles vary per consumer) expect( screen.getByText(children).closest('[data-picasso-popper]') ).toBeInTheDocument() }) }) })