mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 10:00:17 +05:30
feat(renderer): ConfirmDialog (generic title/body/onConfirm)
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useAppStore, type ConfirmProps } from '@/stores/app-store';
|
||||||
|
|
||||||
|
export function ConfirmDialog(props: ConfirmProps) {
|
||||||
|
const closeModal = useAppStore((s) => s.closeModal);
|
||||||
|
const { title, body, confirmLabel = 'Confirm', cancelLabel = 'Cancel', destructive, onConfirm, onCancel } = props;
|
||||||
|
|
||||||
|
const handleConfirm = async () => {
|
||||||
|
await onConfirm();
|
||||||
|
closeModal();
|
||||||
|
};
|
||||||
|
const handleCancel = () => {
|
||||||
|
onCancel?.();
|
||||||
|
closeModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open onOpenChange={(o) => !o && handleCancel()}>
|
||||||
|
<DialogContent aria-describedby="confirm-body">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
<DialogDescription id="confirm-body">{body}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="ghost" onClick={handleCancel}>
|
||||||
|
{cancelLabel}
|
||||||
|
</Button>
|
||||||
|
<Button variant={destructive ? 'destructive' : 'default'} onClick={handleConfirm}>
|
||||||
|
{confirmLabel}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { ConfirmDialog } from '@/components/modals/ConfirmDialog';
|
||||||
|
import { useAppStore } from '@/stores/app-store';
|
||||||
|
|
||||||
|
describe('ConfirmDialog', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
useAppStore.setState({ modal: { kind: null } } as any);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders title, body, and confirm/cancel labels', () => {
|
||||||
|
render(
|
||||||
|
<ConfirmDialog
|
||||||
|
title="Delete file?"
|
||||||
|
body="This cannot be undone."
|
||||||
|
confirmLabel="Delete"
|
||||||
|
onConfirm={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(screen.getByText(/delete file/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/cannot be undone/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: /^delete$/i })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onConfirm and closes on confirm click', async () => {
|
||||||
|
const onConfirm = vi.fn();
|
||||||
|
render(<ConfirmDialog title="T" body="B" onConfirm={onConfirm} />);
|
||||||
|
await userEvent.click(screen.getByRole('button', { name: /confirm/i }));
|
||||||
|
expect(onConfirm).toHaveBeenCalledTimes(1);
|
||||||
|
expect(useAppStore.getState().modal).toEqual({ kind: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('destructive variant uses destructive button class', () => {
|
||||||
|
render(
|
||||||
|
<ConfirmDialog title="T" body="B" destructive onConfirm={() => {}} />
|
||||||
|
);
|
||||||
|
const btn = screen.getByRole('button', { name: /confirm/i });
|
||||||
|
expect(btn.className).toContain('bg-destructive');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user