diff --git a/src/design-system/composites/ConfirmDialog/ConfirmDialog.test.tsx b/src/design-system/composites/ConfirmDialog/ConfirmDialog.test.tsx
new file mode 100644
index 0000000..f554d9a
--- /dev/null
+++ b/src/design-system/composites/ConfirmDialog/ConfirmDialog.test.tsx
@@ -0,0 +1,113 @@
+import { describe, it, expect, vi } from 'vitest'
+import { render, screen, waitFor } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { ConfirmDialog } from './ConfirmDialog'
+
+const defaultProps = {
+ open: true,
+ onClose: vi.fn(),
+ onConfirm: vi.fn(),
+ message: 'Delete user "alice"? This cannot be undone.',
+ confirmText: 'alice',
+}
+
+describe('ConfirmDialog', () => {
+ it('renders title and message when open', () => {
+ render()
+ expect(screen.getByText('Confirm Deletion')).toBeInTheDocument()
+ expect(screen.getByText('Delete user "alice"? This cannot be undone.')).toBeInTheDocument()
+ })
+
+ it('does not render when closed', () => {
+ render()
+ expect(screen.queryByText('Confirm Deletion')).not.toBeInTheDocument()
+ })
+
+ it('renders custom title', () => {
+ render()
+ expect(screen.getByText('Remove item')).toBeInTheDocument()
+ })
+
+ it('shows confirm instruction text', () => {
+ render()
+ expect(screen.getByText(/Type "alice" to confirm/)).toBeInTheDocument()
+ })
+
+ it('disables confirm button until text matches', () => {
+ render()
+ expect(screen.getByRole('button', { name: 'Delete' })).toBeDisabled()
+ })
+
+ it('enables confirm button when text matches', async () => {
+ const user = userEvent.setup()
+ render()
+ await user.type(screen.getByRole('textbox'), 'alice')
+ expect(screen.getByRole('button', { name: 'Delete' })).toBeEnabled()
+ })
+
+ it('calls onConfirm when confirm button is clicked after typing', async () => {
+ const onConfirm = vi.fn()
+ const user = userEvent.setup()
+ render()
+ await user.type(screen.getByRole('textbox'), 'alice')
+ await user.click(screen.getByRole('button', { name: 'Delete' }))
+ expect(onConfirm).toHaveBeenCalledOnce()
+ })
+
+ it('calls onClose when cancel button is clicked', async () => {
+ const onClose = vi.fn()
+ const user = userEvent.setup()
+ render()
+ await user.click(screen.getByRole('button', { name: 'Cancel' }))
+ expect(onClose).toHaveBeenCalledOnce()
+ })
+
+ it('calls onConfirm on Enter when text matches', async () => {
+ const onConfirm = vi.fn()
+ const user = userEvent.setup()
+ render()
+ await user.type(screen.getByRole('textbox'), 'alice')
+ await user.keyboard('{Enter}')
+ expect(onConfirm).toHaveBeenCalledOnce()
+ })
+
+ it('does not call onConfirm on Enter when text does not match', async () => {
+ const onConfirm = vi.fn()
+ const user = userEvent.setup()
+ render()
+ await user.type(screen.getByRole('textbox'), 'alic')
+ await user.keyboard('{Enter}')
+ expect(onConfirm).not.toHaveBeenCalled()
+ })
+
+ it('disables both buttons when loading', async () => {
+ const user = userEvent.setup()
+ render()
+ await user.type(screen.getByRole('textbox'), 'alice')
+ const buttons = screen.getAllByRole('button')
+ for (const btn of buttons) {
+ expect(btn).toBeDisabled()
+ }
+ })
+
+ it('clears input when opened', async () => {
+ const { rerender } = render()
+ rerender()
+ await waitFor(() => {
+ expect(screen.getByRole('textbox')).toHaveValue('')
+ })
+ })
+
+ it('auto-focuses input on open', async () => {
+ render()
+ await waitFor(() => {
+ expect(screen.getByRole('textbox')).toHaveFocus()
+ })
+ })
+
+ it('renders custom button labels', () => {
+ render()
+ expect(screen.getByRole('button', { name: 'Remove' })).toBeInTheDocument()
+ expect(screen.getByRole('button', { name: 'Keep' })).toBeInTheDocument()
+ })
+})