Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions packages/web/src/components/confirm-dialog.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/// <reference types="bun" />

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(
<ConfirmDialog
open
title="Share this connection?"
description="Everyone in the organization will be able to use it."
confirmLabel={destructive ? 'Delete' : 'Share'}
destructive={destructive}
onClose={onClose}
onConfirm={onConfirm}
/>,
);
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);
});
});
47 changes: 29 additions & 18 deletions packages/web/src/components/confirm-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
DialogHeader,
DialogTitle,
} from '@proxy/ui/components/dialog';
import {useCommandEnterSubmit} from '@/hooks/use-command-enter';

type Props = {
open: boolean;
Expand All @@ -28,6 +29,8 @@ export function ConfirmDialog({
onClose,
onConfirm,
}: Props) {
const form = useCommandEnterSubmit(open && !destructive);

return (
<Dialog
open={open}
Expand All @@ -39,24 +42,32 @@ export function ConfirmDialog({
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={onClose}>
Cancel
</Button>
<Button
variant={destructive ? 'destructive' : 'default'}
onClick={() => {
onConfirm();
onClose();
}}
>
{confirmLabel}
</Button>
</DialogFooter>
<form
ref={form}
className="grid gap-4"
onSubmit={(event) => {
event.preventDefault();
onConfirm();
onClose();
}}
>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button type="button" variant="outline" autoFocus={destructive} onClick={onClose}>
Cancel
</Button>
<Button
type="submit"
variant={destructive ? 'destructive' : 'default'}
autoFocus={!destructive}
>
{confirmLabel}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
Expand Down
27 changes: 27 additions & 0 deletions packages/web/src/components/persona-dialog.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/// <reference types="bun" />

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(<PersonaDialog open initial={null} onClose={onClose} onSave={onSave} />);

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);
});
});
3 changes: 3 additions & 0 deletions packages/web/src/components/persona-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -62,6 +63,7 @@ export function PersonaDialog({open, initial, onClose, onSave, onDelete}: Props)
const [error, setError] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
const ready = persona.name.trim() !== '';
const form = useCommandEnterSubmit(ready && !busy);

const finish = (failed: string | null) => {
setBusy(false);
Expand All @@ -83,6 +85,7 @@ export function PersonaDialog({open, initial, onClose, onSave, onDelete}: Props)
>
<DialogContent>
<form
ref={form}
className="grid gap-5"
onSubmit={async (event) => {
event.preventDefault();
Expand Down
Loading