import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { DropdownMenu } from './DropdownMenu';
describe('DropdownMenu', () => {
const onClose = vi.fn();
beforeEach(() => {
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});
});
afterEach(() => {
cleanup();
vi.clearAllMocks();
});
describe('rendering', () => {
it('should not render when isOpen is false', () => {
render(
Trigger}
isOpen={false}
onClose={onClose}
>
Content
,
);
expect(screen.queryByTestId('ockDropdownMenu')).not.toBeInTheDocument();
});
it('should render when isOpen is true', () => {
render(
Trigger} isOpen={true}>
Content
,
);
expect(screen.getByTestId('ockDropdownMenu')).toBeInTheDocument();
expect(screen.getByText('Content')).toBeInTheDocument();
});
it('should handle null trigger gracefully', () => {
render(
Content
,
);
expect(screen.getByTestId('ockDropdownMenu')).toBeInTheDocument();
});
});
describe('positioning', () => {
const alignments = ['start', 'center', 'end'] as const;
for (const align of alignments) {
it(`should position correctly with align=${align}`, () => {
render(
Trigger}
isOpen={true}
align={align}
sideOffset={8}
>
Content
,
);
const dropdown = screen.getByTestId('ockDropdownMenu');
expect(dropdown).toBeInTheDocument();
expect(dropdown.style.top).toBeDefined();
expect(dropdown.style.left).toBeDefined();
});
}
it('should update position on window resize', () => {
render(
Trigger} isOpen={true}>
Content
,
);
fireEvent(window, new Event('resize'));
expect(screen.getByTestId('ockDropdownMenu')).toBeInTheDocument();
});
it('should update position on scroll', () => {
render(
Trigger} isOpen={true}>
Content
,
);
fireEvent.scroll(window);
expect(screen.getByTestId('ockDropdownMenu')).toBeInTheDocument();
});
it('should handle missing getBoundingClientRect gracefully', () => {
const originalGetBoundingClientRect =
Element.prototype.getBoundingClientRect;
Element.prototype.getBoundingClientRect = vi
.fn()
.mockReturnValue(undefined);
render(
Trigger} isOpen={true}>
Content
,
);
const dropdown = screen.getByTestId('ockDropdownMenu');
expect(dropdown).toBeInTheDocument();
Element.prototype.getBoundingClientRect = originalGetBoundingClientRect;
});
it('should handle null trigger and contentRef gracefully', () => {
const originalGetBoundingClientRect =
Element.prototype.getBoundingClientRect;
Element.prototype.getBoundingClientRect = vi
.fn()
.mockReturnValue(undefined);
render(
Content
,
);
const dropdown = screen.getByTestId('ockDropdownMenu');
expect(dropdown).toBeInTheDocument();
Element.prototype.getBoundingClientRect = originalGetBoundingClientRect;
});
});
describe('interactions', () => {
it('should not call onClose when clicking inside', () => {
render(
Trigger}
isOpen={true}
onClose={onClose}
>
,
);
fireEvent.mouseDown(screen.getByText('Menu Item'));
expect(onClose).not.toHaveBeenCalled();
});
it('should call onClose when pressing Escape', () => {
render(
Trigger}
isOpen={true}
onClose={onClose}
>
Content
,
);
fireEvent.keyDown(document.body, { key: 'Escape' });
expect(onClose).toHaveBeenCalled();
});
});
describe('accessibility', () => {
it('should have correct ARIA attributes', () => {
render(
Trigger}
isOpen={true}
aria-label="Test Menu"
>
Content
,
);
const menu = screen.getByTestId('ockDropdownMenu');
expect(menu).toHaveAttribute('role', 'listbox');
expect(menu).toHaveAttribute('aria-label', 'Test Menu');
});
});
});