import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { ConfirmDialog } from '../ConfirmDialog'
// Mock Radix portal to render inline
jest.mock('@radix-ui/react-dialog', () => {
const actual = jest.requireActual('@radix-ui/react-dialog')
return {
...actual,
Portal: ({ children }: { children: React.ReactNode }) => <>{children}>,
}
})
describe('ConfirmDialog', () => {
const defaultProps = {
open: true,
onClose: jest.fn(),
onConfirm: jest.fn(),
title: 'Delete item?',
}
beforeEach(() => {
jest.clearAllMocks()
})
it('renders title', () => {
render()
expect(screen.getByText('Delete item?')).toBeInTheDocument()
})
it('renders string description', () => {
render(
)
expect(screen.getByText('This cannot be undone.')).toBeInTheDocument()
})
it('renders ReactNode description', () => {
render(
Custom content}
/>
)
expect(screen.getByTestId('custom')).toBeInTheDocument()
})
it('renders default button labels', () => {
render()
expect(screen.getByText('Confirm')).toBeInTheDocument()
expect(screen.getByText('Cancel')).toBeInTheDocument()
})
it('renders custom button labels', () => {
render(
)
expect(screen.getByText('Delete')).toBeInTheDocument()
expect(screen.getByText('Keep')).toBeInTheDocument()
})
it('calls onConfirm when confirm button is clicked', () => {
render()
fireEvent.click(screen.getByText('Confirm'))
expect(defaultProps.onConfirm).toHaveBeenCalledTimes(1)
})
it('calls onClose when cancel button is clicked', () => {
render()
fireEvent.click(screen.getByText('Cancel'))
expect(defaultProps.onClose).toHaveBeenCalledTimes(1)
})
it('disables buttons when loading', () => {
render()
const confirmBtn = screen.getByText('Confirm').closest('button')
const cancelBtn = screen.getByText('Cancel').closest('button')
expect(confirmBtn).toBeDisabled()
expect(cancelBtn).toBeDisabled()
})
it('shows spinner when loading', () => {
const { container } = render()
const spinner = container.querySelector('.animate-spin')
expect(spinner).toBeInTheDocument()
})
it('does not call onClose when cancel clicked during loading', () => {
render()
// The button is disabled so the click handler won't fire,
// but also handleClose guards against it
const cancelBtn = screen.getByText('Cancel').closest('button')!
fireEvent.click(cancelBtn)
expect(defaultProps.onClose).not.toHaveBeenCalled()
})
it('applies destructive variant to confirm button', () => {
render()
const confirmBtn = screen.getByText('Confirm').closest('button')
expect(confirmBtn?.className).toMatch(/destructive/)
})
it('applies default variant when not destructive', () => {
render()
const confirmBtn = screen.getByText('Confirm').closest('button')
expect(confirmBtn?.className).not.toMatch(/destructive/)
})
it('does not render body when no description provided', () => {
const { container } = render()
// Body has class py-4, should not exist
expect(container.querySelector('.py-4')).not.toBeInTheDocument()
})
it('handles async onConfirm', async () => {
const asyncConfirm = jest.fn().mockResolvedValue(undefined)
render()
fireEvent.click(screen.getByText('Confirm'))
await waitFor(() => {
expect(asyncConfirm).toHaveBeenCalledTimes(1)
})
})
})