diff --git a/packages/web/src/components/confirm-dialog.test.tsx b/packages/web/src/components/confirm-dialog.test.tsx new file mode 100644 index 00000000..a4e19166 --- /dev/null +++ b/packages/web/src/components/confirm-dialog.test.tsx @@ -0,0 +1,54 @@ +/// + +import {afterEach, describe, expect, mock, test} from 'bun:test'; +import {cleanup, fireEvent, render, screen, waitFor} from '@testing-library/react'; +import {ConfirmDialog} from '@/components/confirm-dialog'; + +afterEach(() => { + cleanup(); +}); + +function renderDialog({destructive = false}: {destructive?: boolean} = {}) { + const onClose = mock(); + const onConfirm = mock(); + render( + , + ); + return {onClose, onConfirm}; +} + +describe('ConfirmDialog', () => { + test('uses the confirmation as the default action when it is safe', async () => { + const {onClose, onConfirm} = renderDialog(); + const confirm = screen.getByRole('button', {name: 'Share'}); + + await waitFor(() => expect(document.activeElement).toBe(confirm)); + expect(confirm.getAttribute('type')).toBe('submit'); + fireEvent.keyDown(window, {key: 'Enter', metaKey: true}); + + expect(onConfirm).toHaveBeenCalledTimes(1); + expect(onClose).toHaveBeenCalledTimes(1); + }); + + test('uses cancel as the default action when confirmation is destructive', async () => { + const {onClose, onConfirm} = renderDialog({destructive: true}); + const cancel = screen.getByRole('button', {name: 'Cancel'}); + + await waitFor(() => expect(document.activeElement).toBe(cancel)); + expect(cancel.getAttribute('type')).toBe('button'); + fireEvent.keyDown(window, {key: 'Enter', metaKey: true}); + expect(onConfirm).not.toHaveBeenCalled(); + fireEvent.click(cancel); + + expect(onConfirm).not.toHaveBeenCalled(); + expect(onClose).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/web/src/components/confirm-dialog.tsx b/packages/web/src/components/confirm-dialog.tsx index 49cc1fca..5c5f451a 100644 --- a/packages/web/src/components/confirm-dialog.tsx +++ b/packages/web/src/components/confirm-dialog.tsx @@ -8,6 +8,7 @@ import { DialogHeader, DialogTitle, } from '@proxy/ui/components/dialog'; +import {useCommandEnterSubmit} from '@/hooks/use-command-enter'; type Props = { open: boolean; @@ -28,6 +29,8 @@ export function ConfirmDialog({ onClose, onConfirm, }: Props) { + const form = useCommandEnterSubmit(open && !destructive); + return ( - - {title} - {description} - - - - - +
{ + event.preventDefault(); + onConfirm(); + onClose(); + }} + > + + {title} + {description} + + + + + +
); diff --git a/packages/web/src/components/persona-dialog.test.tsx b/packages/web/src/components/persona-dialog.test.tsx new file mode 100644 index 00000000..ff984a90 --- /dev/null +++ b/packages/web/src/components/persona-dialog.test.tsx @@ -0,0 +1,27 @@ +/// + +import {afterEach, describe, expect, mock, test} from 'bun:test'; +import {cleanup, fireEvent, render, screen, waitFor} from '@testing-library/react'; + +mock.module('@/components/head', () => ({LiveHead: () => null})); + +const {PersonaDialog} = await import('@/components/persona-dialog'); + +afterEach(() => { + cleanup(); +}); + +describe('PersonaDialog', () => { + test('creates an agent with Command+Enter', async () => { + const onClose = mock(); + const onSave = mock(async () => null); + render(); + + fireEvent.change(screen.getByLabelText('Name'), {target: {value: 'Finance'}}); + fireEvent.keyDown(window, {key: 'Enter', metaKey: true}); + + await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1)); + expect(onSave).toHaveBeenCalledWith(expect.objectContaining({name: 'Finance'})); + expect(onClose).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/web/src/components/persona-dialog.tsx b/packages/web/src/components/persona-dialog.tsx index 6c4e3ee9..c9907bb8 100644 --- a/packages/web/src/components/persona-dialog.tsx +++ b/packages/web/src/components/persona-dialog.tsx @@ -16,6 +16,7 @@ import { import {Input} from '@proxy/ui/components/input'; import {Label} from '@proxy/ui/components/label'; import {cn} from '@proxy/ui/lib/utils'; +import {useCommandEnterSubmit} from '@/hooks/use-command-enter'; type Props = { open: boolean; @@ -62,6 +63,7 @@ export function PersonaDialog({open, initial, onClose, onSave, onDelete}: Props) const [error, setError] = useState(null); const [busy, setBusy] = useState(false); const ready = persona.name.trim() !== ''; + const form = useCommandEnterSubmit(ready && !busy); const finish = (failed: string | null) => { setBusy(false); @@ -83,6 +85,7 @@ export function PersonaDialog({open, initial, onClose, onSave, onDelete}: Props) >
{ event.preventDefault();