import React from 'react' import { fireEvent, render, screen } from '@testing-library/react' import Drawer from './index' import type { IDrawerProps } from './index' // Capture dialog onClose for testing let capturedDialogOnClose: (() => void) | null = null // Mock react-i18next jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key, }), })) // Mock @headlessui/react jest.mock('@headlessui/react', () => ({ Dialog: ({ children, open, onClose, className, unmount }: { children: React.ReactNode open: boolean onClose: () => void className: string unmount: boolean }) => { capturedDialogOnClose = onClose if (!open) return null return (
{children}
) }, DialogBackdrop: ({ children, className, onClick }: { children?: React.ReactNode className: string onClick: () => void }) => (
{children}
), DialogTitle: ({ children, as: _as, className, ...props }: { children: React.ReactNode as?: string className?: string }) => (
{children}
), })) // Mock XMarkIcon jest.mock('@heroicons/react/24/outline', () => ({ XMarkIcon: ({ className, onClick }: { className: string; onClick?: () => void }) => ( ), })) // Helper function to render Drawer with default props const defaultProps: IDrawerProps = { isOpen: true, onClose: jest.fn(), children:
Content
, } const renderDrawer = (props: Partial = {}) => { const mergedProps = { ...defaultProps, ...props } return render() } describe('Drawer', () => { beforeEach(() => { jest.clearAllMocks() capturedDialogOnClose = null }) // Basic rendering tests describe('Rendering', () => { it('should render when isOpen is true', () => { // Arrange & Act renderDrawer({ isOpen: true }) // Assert expect(screen.getByRole('dialog')).toBeInTheDocument() expect(screen.getByTestId('drawer-content')).toBeInTheDocument() }) it('should not render when isOpen is false', () => { // Arrange & Act renderDrawer({ isOpen: false }) // Assert expect(screen.queryByRole('dialog')).not.toBeInTheDocument() }) it('should render children content', () => { // Arrange const childContent =

Custom Content

// Act renderDrawer({ children: childContent }) // Assert expect(screen.getByTestId('custom-child')).toBeInTheDocument() expect(screen.getByText('Custom Content')).toBeInTheDocument() }) }) // Title and description tests describe('Title and Description', () => { it('should render title when provided', () => { // Arrange & Act renderDrawer({ title: 'Test Title' }) // Assert expect(screen.getByText('Test Title')).toBeInTheDocument() }) it('should not render title when not provided', () => { // Arrange & Act renderDrawer({ title: '' }) // Assert const titles = screen.queryAllByTestId('dialog-title') const titleWithText = titles.find(el => el.textContent !== '') expect(titleWithText).toBeUndefined() }) it('should render description when provided', () => { // Arrange & Act renderDrawer({ description: 'Test Description' }) // Assert expect(screen.getByText('Test Description')).toBeInTheDocument() }) it('should not render description when not provided', () => { // Arrange & Act renderDrawer({ description: '' }) // Assert expect(screen.queryByText('Test Description')).not.toBeInTheDocument() }) it('should render both title and description together', () => { // Arrange & Act renderDrawer({ title: 'My Title', description: 'My Description', }) // Assert expect(screen.getByText('My Title')).toBeInTheDocument() expect(screen.getByText('My Description')).toBeInTheDocument() }) }) // Close button tests describe('Close Button', () => { it('should render close icon when showClose is true', () => { // Arrange & Act renderDrawer({ showClose: true }) // Assert expect(screen.getByTestId('close-icon')).toBeInTheDocument() }) it('should not render close icon when showClose is false', () => { // Arrange & Act renderDrawer({ showClose: false }) // Assert expect(screen.queryByTestId('close-icon')).not.toBeInTheDocument() }) it('should not render close icon by default', () => { // Arrange & Act renderDrawer({}) // Assert expect(screen.queryByTestId('close-icon')).not.toBeInTheDocument() }) it('should call onClose when close icon is clicked', () => { // Arrange const onClose = jest.fn() renderDrawer({ showClose: true, onClose }) // Act fireEvent.click(screen.getByTestId('close-icon')) // Assert expect(onClose).toHaveBeenCalledTimes(1) }) }) // Backdrop/Mask tests describe('Backdrop and Mask', () => { it('should render backdrop when noOverlay is false', () => { // Arrange & Act renderDrawer({ noOverlay: false }) // Assert expect(screen.getByTestId('dialog-backdrop')).toBeInTheDocument() }) it('should not render backdrop when noOverlay is true', () => { // Arrange & Act renderDrawer({ noOverlay: true }) // Assert expect(screen.queryByTestId('dialog-backdrop')).not.toBeInTheDocument() }) it('should apply mask background when mask is true', () => { // Arrange & Act renderDrawer({ mask: true }) // Assert const backdrop = screen.getByTestId('dialog-backdrop') expect(backdrop.className).toContain('bg-black/30') }) it('should not apply mask background when mask is false', () => { // Arrange & Act renderDrawer({ mask: false }) // Assert const backdrop = screen.getByTestId('dialog-backdrop') expect(backdrop.className).not.toContain('bg-black/30') }) it('should call onClose when backdrop is clicked and clickOutsideNotOpen is false', () => { // Arrange const onClose = jest.fn() renderDrawer({ onClose, clickOutsideNotOpen: false }) // Act fireEvent.click(screen.getByTestId('dialog-backdrop')) // Assert expect(onClose).toHaveBeenCalledTimes(1) }) it('should not call onClose when backdrop is clicked and clickOutsideNotOpen is true', () => { // Arrange const onClose = jest.fn() renderDrawer({ onClose, clickOutsideNotOpen: true }) // Act fireEvent.click(screen.getByTestId('dialog-backdrop')) // Assert expect(onClose).not.toHaveBeenCalled() }) }) // Footer tests describe('Footer', () => { it('should render default footer with cancel and save buttons when footer is undefined', () => { // Arrange & Act renderDrawer({ footer: undefined }) // Assert expect(screen.getByText('common.operation.cancel')).toBeInTheDocument() expect(screen.getByText('common.operation.save')).toBeInTheDocument() }) it('should not render footer when footer is null', () => { // Arrange & Act renderDrawer({ footer: null }) // Assert expect(screen.queryByText('common.operation.cancel')).not.toBeInTheDocument() expect(screen.queryByText('common.operation.save')).not.toBeInTheDocument() }) it('should render custom footer when provided', () => { // Arrange const customFooter =
Custom Footer
// Act renderDrawer({ footer: customFooter }) // Assert expect(screen.getByTestId('custom-footer')).toBeInTheDocument() expect(screen.queryByText('common.operation.cancel')).not.toBeInTheDocument() }) it('should call onCancel when cancel button is clicked', () => { // Arrange const onCancel = jest.fn() renderDrawer({ onCancel }) // Act const cancelButton = screen.getByText('common.operation.cancel') fireEvent.click(cancelButton) // Assert expect(onCancel).toHaveBeenCalledTimes(1) }) it('should call onOk when save button is clicked', () => { // Arrange const onOk = jest.fn() renderDrawer({ onOk }) // Act const saveButton = screen.getByText('common.operation.save') fireEvent.click(saveButton) // Assert expect(onOk).toHaveBeenCalledTimes(1) }) it('should not throw when onCancel is not provided and cancel is clicked', () => { // Arrange renderDrawer({ onCancel: undefined }) // Act & Assert expect(() => { fireEvent.click(screen.getByText('common.operation.cancel')) }).not.toThrow() }) it('should not throw when onOk is not provided and save is clicked', () => { // Arrange renderDrawer({ onOk: undefined }) // Act & Assert expect(() => { fireEvent.click(screen.getByText('common.operation.save')) }).not.toThrow() }) }) // Custom className tests describe('Custom ClassNames', () => { it('should apply custom dialogClassName', () => { // Arrange & Act renderDrawer({ dialogClassName: 'custom-dialog-class' }) // Assert expect(screen.getByRole('dialog').className).toContain('custom-dialog-class') }) it('should apply custom dialogBackdropClassName', () => { // Arrange & Act renderDrawer({ dialogBackdropClassName: 'custom-backdrop-class' }) // Assert expect(screen.getByTestId('dialog-backdrop').className).toContain('custom-backdrop-class') }) it('should apply custom containerClassName', () => { // Arrange & Act const { container } = renderDrawer({ containerClassName: 'custom-container-class' }) // Assert const containerDiv = container.querySelector('.custom-container-class') expect(containerDiv).toBeInTheDocument() }) it('should apply custom panelClassName', () => { // Arrange & Act const { container } = renderDrawer({ panelClassName: 'custom-panel-class' }) // Assert const panelDiv = container.querySelector('.custom-panel-class') expect(panelDiv).toBeInTheDocument() }) }) // Position tests describe('Position', () => { it('should apply center position class when positionCenter is true', () => { // Arrange & Act const { container } = renderDrawer({ positionCenter: true }) // Assert const containerDiv = container.querySelector('.\\!justify-center') expect(containerDiv).toBeInTheDocument() }) it('should use end position by default when positionCenter is false', () => { // Arrange & Act const { container } = renderDrawer({ positionCenter: false }) // Assert const containerDiv = container.querySelector('.justify-end') expect(containerDiv).toBeInTheDocument() }) }) // Unmount prop tests describe('Unmount Prop', () => { it('should pass unmount prop to Dialog component', () => { // Arrange & Act renderDrawer({ unmount: true }) // Assert expect(screen.getByTestId('dialog').getAttribute('data-unmount')).toBe('true') }) it('should default unmount to false', () => { // Arrange & Act renderDrawer({}) // Assert expect(screen.getByTestId('dialog').getAttribute('data-unmount')).toBe('false') }) }) // Edge cases describe('Edge Cases', () => { it('should handle empty string title', () => { // Arrange & Act renderDrawer({ title: '' }) // Assert expect(screen.getByRole('dialog')).toBeInTheDocument() }) it('should handle empty string description', () => { // Arrange & Act renderDrawer({ description: '' }) // Assert expect(screen.getByRole('dialog')).toBeInTheDocument() }) it('should handle special characters in title', () => { // Arrange const specialTitle = '' // Act renderDrawer({ title: specialTitle }) // Assert expect(screen.getByText(specialTitle)).toBeInTheDocument() }) it('should handle very long title', () => { // Arrange const longTitle = 'A'.repeat(500) // Act renderDrawer({ title: longTitle }) // Assert expect(screen.getByText(longTitle)).toBeInTheDocument() }) it('should handle complex children with multiple elements', () => { // Arrange const complexChildren = (

Heading

Paragraph

) // Act renderDrawer({ children: complexChildren }) // Assert expect(screen.getByTestId('complex-children')).toBeInTheDocument() expect(screen.getByText('Heading')).toBeInTheDocument() expect(screen.getByText('Paragraph')).toBeInTheDocument() expect(screen.getByTestId('input-element')).toBeInTheDocument() expect(screen.getByTestId('button-element')).toBeInTheDocument() }) it('should handle null children gracefully', () => { // Arrange & Act renderDrawer({ children: null as unknown as React.ReactNode }) // Assert expect(screen.getByRole('dialog')).toBeInTheDocument() }) it('should handle undefined footer without crashing', () => { // Arrange & Act renderDrawer({ footer: undefined }) // Assert expect(screen.getByRole('dialog')).toBeInTheDocument() }) it('should handle rapid open/close toggles', () => { // Arrange const onClose = jest.fn() const { rerender } = render(
Content
, ) // Act - Toggle multiple times rerender(
Content
, ) rerender(
Content
, ) rerender(
Content
, ) // Assert expect(screen.queryByRole('dialog')).not.toBeInTheDocument() }) }) // Combined prop scenarios describe('Combined Prop Scenarios', () => { it('should render with all optional props', () => { // Arrange & Act renderDrawer({ title: 'Full Feature Title', description: 'Full Feature Description', dialogClassName: 'custom-dialog', dialogBackdropClassName: 'custom-backdrop', containerClassName: 'custom-container', panelClassName: 'custom-panel', showClose: true, mask: true, positionCenter: true, unmount: true, noOverlay: false, footer:
Footer
, }) // Assert expect(screen.getByRole('dialog')).toBeInTheDocument() expect(screen.getByText('Full Feature Title')).toBeInTheDocument() expect(screen.getByText('Full Feature Description')).toBeInTheDocument() expect(screen.getByTestId('close-icon')).toBeInTheDocument() expect(screen.getByTestId('custom-full-footer')).toBeInTheDocument() }) it('should render minimal drawer with only required props', () => { // Arrange const minimalProps: IDrawerProps = { isOpen: true, onClose: jest.fn(), children:
Minimal Content
, } // Act render() // Assert expect(screen.getByRole('dialog')).toBeInTheDocument() expect(screen.getByText('Minimal Content')).toBeInTheDocument() }) it('should handle showClose with title simultaneously', () => { // Arrange & Act renderDrawer({ title: 'Title with Close', showClose: true, }) // Assert expect(screen.getByText('Title with Close')).toBeInTheDocument() expect(screen.getByTestId('close-icon')).toBeInTheDocument() }) it('should handle noOverlay with clickOutsideNotOpen', () => { // Arrange const onClose = jest.fn() // Act renderDrawer({ noOverlay: true, clickOutsideNotOpen: true, onClose, }) // Assert - backdrop should not exist expect(screen.queryByTestId('dialog-backdrop')).not.toBeInTheDocument() }) }) // Dialog onClose callback tests (e.g., Escape key) describe('Dialog onClose Callback', () => { it('should call onClose when Dialog triggers close and clickOutsideNotOpen is false', () => { // Arrange const onClose = jest.fn() renderDrawer({ onClose, clickOutsideNotOpen: false }) // Act - Simulate Dialog's onClose (e.g., pressing Escape) capturedDialogOnClose?.() // Assert expect(onClose).toHaveBeenCalledTimes(1) }) it('should not call onClose when Dialog triggers close and clickOutsideNotOpen is true', () => { // Arrange const onClose = jest.fn() renderDrawer({ onClose, clickOutsideNotOpen: true }) // Act - Simulate Dialog's onClose (e.g., pressing Escape) capturedDialogOnClose?.() // Assert expect(onClose).not.toHaveBeenCalled() }) it('should call onClose by default when Dialog triggers close', () => { // Arrange const onClose = jest.fn() renderDrawer({ onClose }) // Act capturedDialogOnClose?.() // Assert expect(onClose).toHaveBeenCalledTimes(1) }) }) // Event handler interaction tests describe('Event Handler Interactions', () => { it('should handle multiple consecutive close icon clicks', () => { // Arrange const onClose = jest.fn() renderDrawer({ showClose: true, onClose }) // Act const closeIcon = screen.getByTestId('close-icon') fireEvent.click(closeIcon) fireEvent.click(closeIcon) fireEvent.click(closeIcon) // Assert expect(onClose).toHaveBeenCalledTimes(3) }) it('should handle onCancel and onOk being the same function', () => { // Arrange const handler = jest.fn() renderDrawer({ onCancel: handler, onOk: handler }) // Act fireEvent.click(screen.getByText('common.operation.cancel')) fireEvent.click(screen.getByText('common.operation.save')) // Assert expect(handler).toHaveBeenCalledTimes(2) }) }) })