import { describe, it, expect, vi, afterEach } from 'vitest';
import '@testing-library/jest-dom/vitest';
import { render, cleanup, fireEvent } from '@solidjs/testing-library';
import { Popover } from './popover';
afterEach(cleanup);
// createPresence unmounts on a microtask when there is no exit animation (jsdom),
// so flush the queue before asserting a closed popover has left the DOM.
const tick = () => new Promise((r) => setTimeout(r, 0));
describe('Popover', () => {
it('renders the trigger and hides the content until opened', () => {
const { getByText, queryByText } = render(() => (
Open menu}>
Panel body
));
expect(getByText('Open menu')).toBeInTheDocument();
expect(queryByText('Panel body')).not.toBeInTheDocument();
});
it('opens on trigger click and calls onOpenChange(true)', () => {
const onOpenChange = vi.fn();
const { getByText, queryByText } = render(() => (
Open menu} onOpenChange={onOpenChange}>
Panel body
));
fireEvent.click(getByText('Open menu'));
expect(queryByText('Panel body')).toBeInTheDocument();
expect(onOpenChange).toHaveBeenCalledWith(true);
});
it('toggles closed on a second trigger click and calls onOpenChange(false)', async () => {
const onOpenChange = vi.fn();
const { getByText, queryByText } = render(() => (
Open menu} onOpenChange={onOpenChange}>
Panel body
));
const trigger = getByText('Open menu');
fireEvent.click(trigger);
expect(queryByText('Panel body')).toBeInTheDocument();
fireEvent.click(trigger);
await tick();
expect(queryByText('Panel body')).not.toBeInTheDocument();
expect(onOpenChange).toHaveBeenLastCalledWith(false);
});
it('closes on Escape', async () => {
const { getByText, queryByText } = render(() => (
Open menu}>
Panel body
));
fireEvent.click(getByText('Open menu'));
expect(queryByText('Panel body')).toBeInTheDocument();
fireEvent.keyDown(document, { key: 'Escape' });
await tick();
expect(queryByText('Panel body')).not.toBeInTheDocument();
});
it('stays open when clicking inside the panel, closes on an outside pointerdown', async () => {
const { getByText, queryByText } = render(() => (
Open menu}>
Panel body
));
fireEvent.click(getByText('Open menu'));
fireEvent.pointerDown(getByText('Panel body'));
expect(queryByText('Panel body')).toBeInTheDocument();
fireEvent.pointerDown(document.body);
await tick();
expect(queryByText('Panel body')).not.toBeInTheDocument();
});
it('respects the controlled open prop', () => {
const { queryByText } = render(() => (
Open menu}>
Panel body
));
expect(queryByText('Panel body')).toBeInTheDocument();
});
it('does not change its own state when controlled — defers to onOpenChange', () => {
const onOpenChange = vi.fn();
const { getByText, queryByText } = render(() => (
Open menu} onOpenChange={onOpenChange}>
Panel body
));
fireEvent.click(getByText('Open menu'));
// controlled: stays closed until the parent flips `open`
expect(queryByText('Panel body')).not.toBeInTheDocument();
expect(onOpenChange).toHaveBeenCalledWith(true);
});
});