import { render, screen, fireEvent } from '@testing-library/react' import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest' import { ResponsiveDrawer, resolveOrigins } from './responsive-drawer' import { ThemeProvider } from '@mui/material' import { createTheme } from '@mui/material/styles' // Create a basic theme for testing const theme = createTheme() describe('ResponsiveDrawer', () => { const mockOnChangeCollapsed = vi.fn() // A real anchor element: the desktop Popover only opens when `ref != null` // (the open-by-default guard), and positioning needs a DOM node. let anchor: HTMLElement const defaultProps = { collapsed: false, onChangeCollapsed: mockOnChangeCollapsed, position: 'top-left' as const, isMobile: false, children:
Test Content
, } beforeEach(() => { mockOnChangeCollapsed.mockReset() anchor = document.createElement('div') document.body.appendChild(anchor) }) afterEach(() => { anchor.remove() }) test('renders in desktop mode when isMobile is false', () => { const { container } = render( , ) // Check if content is rendered const content = screen.getByTestId('drawer-content') expect(content).toBeDefined() // Make sure we're not using the mobile Drawer (no presentation role) const presentationElements = container.querySelectorAll( '[role="presentation"]', ) expect(presentationElements.length).toBe(0) }) test('does not open the desktop popover until the anchor ref resolves', () => { // Open-by-default guard: collapsed=false but ref=null → stays closed so the // popover never renders with a null anchorEl. render( , ) expect(screen.queryByTestId('drawer-content')).toBeNull() }) test('renders in mobile mode when isMobile is true', () => { render( , ) // Check if content is rendered const content = screen.getByTestId('drawer-content') expect(content).toBeDefined() // Mobile mode uses MUI Drawer which has a 'presentation' role const presentationElements = document.querySelectorAll( '[role="presentation"]', ) expect(presentationElements.length).toBeGreaterThan(0) }) test('hides content when collapsed is true in desktop mode', () => { render( , ) // When collapsed, the Popover should be closed and content should not be in the document const content = screen.queryByTestId('drawer-content') expect(content).toBeNull() }) test('closes drawer when clicking outside in mobile mode', () => { const { container } = render( , ) // Find and click the backdrop to close the drawer const backdrop = container.querySelector('[role="presentation"] > div') expect(backdrop).toBeDefined() if (backdrop) { fireEvent.click(backdrop) // onChangeCollapsed should be called to close the drawer expect(mockOnChangeCollapsed).toHaveBeenCalledTimes(1) expect(mockOnChangeCollapsed).toHaveBeenCalledWith(true) } }) // Each of the four `position` values produces a different // anchorOrigin/transformOrigin combination. test.each(['top-left', 'top-right', 'bottom-left', 'bottom-right'] as const)( 'renders in desktop mode with position=%s', (position) => { expect(() => render( , ), ).not.toThrow() }, ) test('modal (default): renders an (invisible) backdrop', () => { render( , ) // The popover portals to document.body, so query the whole document. expect(document.querySelector('.MuiBackdrop-root')).not.toBeNull() }) test('forwards slotProps to the underlying Popover', () => { const slotProps = { paper: { 'data-testid': 'paper-slot' } } as never expect(() => render( , ), ).not.toThrow() }) test('defaults to portaling into document.body when container is omitted', () => { render( , ) const content = screen.getByTestId('drawer-content') const modalRoot = content.closest('.MuiModal-root') expect(modalRoot?.parentElement).toBe(document.body) }) test('portals into the given container instead of document.body', () => { const customContainer = document.createElement('div') document.body.appendChild(customContainer) try { render( , ) const content = screen.getByTestId('drawer-content') const modalRoot = content.closest('.MuiModal-root') expect(modalRoot?.parentElement).toBe(customContainer) } finally { customContainer.remove() } }) test('caps the popover paper height at 90% of a measured container element', () => { const customContainer = document.createElement('div') Object.defineProperty(customContainer, 'clientHeight', { value: 500, configurable: true, }) document.body.appendChild(customContainer) try { render( , ) const paper = document.querySelector('.MuiPopover-paper')! expect(paper.style.maxHeight).toBe('450px') expect(paper.style.overflow).toBe('hidden') expect(paper.style.display).toBe('flex') expect(paper.style.flexDirection).toBe('column') const content = screen.getByTestId('drawer-content') const wrapper = content.parentElement! expect(wrapper.style.display).toBe('flex') expect(wrapper.style.flexDirection).toBe('column') expect(wrapper.style.flex).toBe('1 1 0%') expect(wrapper.style.minHeight).toBe('0') expect(wrapper.style.maxHeight).toBe('100%') expect(wrapper.style.overflow).toBe('hidden') } finally { customContainer.remove() } }) test('leaves paper maxHeight unset when no container is given', () => { render( , ) const paper = document.querySelector('.MuiPopover-paper')! expect(paper.style.maxHeight).toBe('') expect(paper.style.display).toBe('') const content = screen.getByTestId('drawer-content') expect(content.parentElement!.style.display).toBe('') }) test('leaves paper maxHeight unset when the container reports height 0', () => { const customContainer = document.createElement('div') Object.defineProperty(customContainer, 'clientHeight', { value: 0, configurable: true, }) document.body.appendChild(customContainer) try { render( , ) const paper = document.querySelector('.MuiPopover-paper')! expect(paper.style.maxHeight).toBe('') } finally { customContainer.remove() } }) test('preserves slotProps.root when capping paper height under modal=true', () => { const customContainer = document.createElement('div') Object.defineProperty(customContainer, 'clientHeight', { value: 500, configurable: true, }) document.body.appendChild(customContainer) try { render( , ) expect(screen.getByTestId('drawer-root-slot')).toBeDefined() expect(screen.getByTestId('drawer-paper-slot')).toBeDefined() const paper = document.querySelector('.MuiPopover-paper')! expect(paper.style.maxHeight).toBe('450px') } finally { customContainer.remove() } }) test('respects sx prop on the Popover', () => { expect(() => render( , ), ).not.toThrow() }) // ── Non-modal mode (modal={false}) ───────────────────────────────────────── test('non-modal: renders no backdrop', () => { render( , ) expect(document.querySelector('.MuiBackdrop-root')).toBeNull() // Content still renders. expect(screen.getByTestId('drawer-content')).toBeDefined() }) test('non-modal: Escape does not close the panel', () => { render( , ) // Fire on a node inside the Modal root so it reaches the Modal's key handler. fireEvent.keyDown(screen.getByTestId('drawer-content'), { key: 'Escape', code: 'Escape', }) expect(mockOnChangeCollapsed).not.toHaveBeenCalled() }) test('modal (default): Escape closes the panel', () => { render( , ) fireEvent.keyDown(screen.getByTestId('drawer-content'), { key: 'Escape', code: 'Escape', }) expect(mockOnChangeCollapsed).toHaveBeenCalledWith(true) }) }) // ── resolveOrigins (pure) ───────────────────────────────────────────────────── describe('resolveOrigins', () => { test('side placement opens beside the trigger (horizontal flip)', () => { expect(resolveOrigins('bottom-right', 'side')).toEqual({ anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, transformOrigin: { vertical: 'bottom', horizontal: 'right' }, }) expect(resolveOrigins('top-left', 'side')).toEqual({ anchorOrigin: { vertical: 'top', horizontal: 'right' }, transformOrigin: { vertical: 'top', horizontal: 'left' }, }) }) test('overlay placement pins corner-to-corner (panel sits on top of the anchor)', () => { // anchorOrigin === transformOrigin for every corner → the panel lands on the // anchor's own corner (it's hidden while open). for (const position of [ 'top-left', 'top-right', 'bottom-left', 'bottom-right', ] as const) { const { anchorOrigin, transformOrigin } = resolveOrigins( position, 'overlay', ) expect(anchorOrigin).toEqual(transformOrigin) } expect(resolveOrigins('bottom-right', 'overlay').anchorOrigin).toEqual({ vertical: 'bottom', horizontal: 'right', }) expect(resolveOrigins('top-left', 'overlay').anchorOrigin).toEqual({ vertical: 'top', horizontal: 'left', }) }) test('transformOrigin is independent of placement (panel keeps its own corner)', () => { for (const position of [ 'top-left', 'top-right', 'bottom-left', 'bottom-right', ] as const) { const side = resolveOrigins(position, 'side') const overlay = resolveOrigins(position, 'overlay') expect(overlay.transformOrigin).toEqual(side.transformOrigin) } }) })