import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { Popover } from './popover';
import type {} from '../../types/popover';
// Mock showPopover and hidePopover methods
beforeEach(() => {
HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
this.setAttribute('data-popover-open', 'true');
}) as unknown as () => void;
HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
this.removeAttribute('data-popover-open');
}) as unknown as () => void;
HTMLElement.prototype.togglePopover = vi.fn(function (this: HTMLElement) {
if (this.hasAttribute('data-popover-open')) {
this.removeAttribute('data-popover-open');
} else {
this.setAttribute('data-popover-open', 'true');
}
}) as unknown as (force?: boolean) => void;
});
describe('Popover', () => {
it('renders trigger button with default label', () => {
render(
Content
);
const trigger = screen.getByRole('button', { name: 'Open Menu' });
expect(trigger).toBeInTheDocument();
expect(trigger).toHaveAttribute('popovertarget', 'test-popover');
});
it('renders popover content with correct attributes', () => {
render(
Popover content
);
const popover = screen.getByText('Popover content').closest('[popover]');
expect(popover).toBeInTheDocument();
expect(popover).toHaveAttribute('popover', 'auto');
expect(popover).toHaveAttribute('data-placement', 'bottom');
});
it('uses custom trigger element', () => {
render(
Custom Trigger}>
Content
);
const trigger = screen.getByRole('button', { name: 'Custom Trigger' });
expect(trigger).toBeInTheDocument();
expect(trigger).toHaveAttribute('popovertarget', 'test-popover');
});
it('shows close button in manual mode by default', () => {
render(
Content
);
const closeButton = screen.getByRole('button', { name: 'Close' });
expect(closeButton).toBeInTheDocument();
expect(closeButton).toHaveAttribute('popovertargetaction', 'hide');
});
it('hides close button in auto mode by default', () => {
render(
Content
);
const closeButton = screen.queryByRole('button', { name: 'Close' });
expect(closeButton).not.toBeInTheDocument();
});
it('respects showCloseButton prop override', () => {
render(
Content
);
const closeButton = screen.getByRole('button', { name: 'Close' });
expect(closeButton).toBeInTheDocument();
});
it('shows arrow by default', () => {
const { container } = render(
Content
);
const arrow = container.querySelector('.fpkit-popover-arrow');
expect(arrow).toBeInTheDocument();
});
it('hides arrow when showArrow is false', () => {
const { container } = render(
Content
);
const arrow = container.querySelector('.fpkit-popover-arrow');
expect(arrow).not.toBeInTheDocument();
});
it('applies custom className', () => {
const { container } = render(
Content
);
const popover = container.querySelector('.fpkit-popover.custom-class');
expect(popover).toBeInTheDocument();
});
it('applies inline styles', () => {
const customStyles = { '--popover-bg': '#000000' } as React.CSSProperties;
const { container } = render(
Content
);
const popover = container.querySelector('.fpkit-popover') as HTMLElement;
expect(popover.style.getPropertyValue('--popover-bg')).toBe('#000000');
});
it('calls onToggle callback when popover state changes', async () => {
const handleToggle = vi.fn();
const { container } = render(
Content
);
const popover = container.querySelector('[popover]') as HTMLElement;
// Simulate toggle event - open
const toggleEventOpen = Object.assign(new Event('toggle'), {
newState: 'open' as const,
oldState: 'closed' as const,
}) as ToggleEvent;
popover.dispatchEvent(toggleEventOpen);
await waitFor(() => {
expect(handleToggle).toHaveBeenCalledWith(true);
});
// Simulate close
const toggleEventClose = Object.assign(new Event('toggle'), {
newState: 'closed' as const,
oldState: 'open' as const,
}) as ToggleEvent;
popover.dispatchEvent(toggleEventClose);
await waitFor(() => {
expect(handleToggle).toHaveBeenCalledWith(false);
});
});
it('handles controlled state with isOpen prop', async () => {
const { rerender, container } = render(
Content
);
const popover = container.querySelector('[popover]') as HTMLElement;
// Initially closed
expect(popover.hasAttribute('data-popover-open')).toBe(false);
// Open popover
rerender(
Content
);
await waitFor(() => {
expect(HTMLElement.prototype.showPopover).toHaveBeenCalled();
});
});
it('generates unique ID when not provided', () => {
const { container } = render(
Content
);
const popover = container.querySelector('[popover]');
const trigger = screen.getByRole('button');
expect(popover).toHaveAttribute('id');
expect(trigger).toHaveAttribute('popovertarget');
const popoverId = popover?.getAttribute('id');
const triggerId = trigger.getAttribute('popovertarget');
expect(popoverId).toBe(triggerId);
});
it('uses provided ID', () => {
render(
Content
);
const trigger = screen.getByRole('button');
expect(trigger).toHaveAttribute('popovertarget', 'custom-id');
});
it('custom close button label', () => {
render(
Content
);
const closeButton = screen.getByRole('button', { name: 'Dismiss' });
expect(closeButton).toBeInTheDocument();
});
it('renders arrow with correct placement attribute', () => {
const { container } = render(
Content
);
const arrow = container.querySelector('.fpkit-popover-arrow');
expect(arrow).toHaveAttribute('data-placement', 'top');
});
});