diff --git a/src/renderer/components/modals/CrashReportModal.tsx b/src/renderer/components/modals/CrashReportModal.tsx new file mode 100644 index 0000000..eae895a --- /dev/null +++ b/src/renderer/components/modals/CrashReportModal.tsx @@ -0,0 +1,47 @@ +import { useEffect, useState } from 'react'; +import { ipc } from '@/lib/ipc'; + +interface Dump { + filename: string; + kind: string; + message?: string; + timestamp: string; +} + +export function CrashReportModal({ onClose }: { onClose: () => void }) { + const [dumps, setDumps] = useState([]); + + const refresh = async () => setDumps(await ipc.crash.read()); + useEffect(() => { refresh(); }, []); + + return ( +
+
+
+

Crash reports

+ +
+ + {dumps.length === 0 ? ( +

No crashes recorded — nice work!

+ ) : ( +
    + {dumps.map((d) => ( +
  • +
    +
    {d.timestamp}
    +
    {d.message ?? '(no message)'}
    +
    + +
  • + ))} +
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/tests/component/modals/CrashReportModal.test.tsx b/tests/component/modals/CrashReportModal.test.tsx new file mode 100644 index 0000000..a8b0483 --- /dev/null +++ b/tests/component/modals/CrashReportModal.test.tsx @@ -0,0 +1,43 @@ +import { describe, expect, test, vi } from 'vitest'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { CrashReportModal } from '@/components/modals/CrashReportModal'; +import { ipc } from '@/lib/ipc'; + +vi.mock('@/lib/ipc', () => ({ + ipc: { + crash: { read: vi.fn(), openDir: vi.fn(), delete: vi.fn() }, + }, +})); + +describe('CrashReportModal', () => { + test('shows empty state when no crashes', async () => { + (ipc.crash.read as any).mockResolvedValue([]); + render( {}} />); + expect(await screen.findByText(/no crashes/i)).toBeInTheDocument(); + }); + + test('lists crashes returned by ipc', async () => { + (ipc.crash.read as any).mockResolvedValue([ + { filename: '1700000000000-1-uncaughtException.json', kind: 'uncaughtException', message: 'boom', timestamp: '2026-01-01T00:00:00.000Z' }, + ]); + render( {}} />); + expect(await screen.findByText(/boom/)).toBeInTheDocument(); + }); + + test('delete button calls ipc.crash.delete', async () => { + (ipc.crash.read as any).mockResolvedValue([ + { filename: '1700000000000-1-uncaughtException.json', kind: 'uncaughtException', message: 'boom', timestamp: '2026-01-01T00:00:00.000Z' }, + ]); + render( {}} />); + const btn = await screen.findByText(/delete/i); + fireEvent.click(btn); + await waitFor(() => expect(ipc.crash.delete).toHaveBeenCalledWith('1700000000000-1-uncaughtException.json')); + }); + + test('open folder button calls ipc.crash.openDir', async () => { + (ipc.crash.read as any).mockResolvedValue([]); + render( {}} />); + fireEvent.click(await screen.findByText(/open dump folder/i)); + expect(ipc.crash.openDir).toHaveBeenCalled(); + }); +}); \ No newline at end of file