Files
markdown-converter/tests/component/modals/CrashReportModal.test.tsx
amitwh 6da2ba7cc7 fix(ipc): crash modal reads wrapper, search wired to real handler
CrashReportModal crashed on dumps.map because ipc.crash.read returns
{ ok, data } and the consumer was assigning the wrapper to state. Now
unwraps .data and falls back to [] on error.

ipc.file.search was miswired to ipc.file.pickFile — clicking 'Search'
popped a file picker instead of searching. Add real 'search-in-files'
handler in src/main/files/search-in-files.js with regex support,
case-sensitivity toggle, .git/node_modules/dist skip, 2MB file cap, and
1000-result limit. Wire preload bridge + TS declaration + allowlist.

Also:
- Drop dead ipc.file.open wrapper that was miswired to pickFile
- Rename FileEntry field 'modified' to 'modifiedAt' (number) so the
  IPC payload matches the declared type
- Update CrashReportModal tests to use the safeCall envelope shape
2026-07-23 09:34:57 +05:30

78 lines
2.6 KiB
TypeScript

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() },
},
}));
/**
* The crash:read IPC handler returns a plain array; ipc.crash.read wraps it
* in { ok: true, data }. The component must unwrap .data before rendering
* — otherwise dumps.length is undefined and dumps.map blows up.
*/
function wrap(data: unknown) {
return { ok: true, data };
}
describe('CrashReportModal', () => {
test('shows empty state when no crashes', async () => {
(ipc.crash.read as any).mockResolvedValue(wrap([]));
render(<CrashReportModal onClose={() => {}} />);
expect(await screen.findByText(/no crashes/i)).toBeInTheDocument();
});
test('shows empty state when ipc returns ok:false', async () => {
(ipc.crash.read as any).mockResolvedValue({
ok: false,
error: { code: 'NO_BRIDGE', message: 'no electronAPI' },
});
render(<CrashReportModal onClose={() => {}} />);
expect(await screen.findByText(/no crashes/i)).toBeInTheDocument();
});
test('lists crashes returned by ipc', async () => {
(ipc.crash.read as any).mockResolvedValue(
wrap([
{
filename: '1700000000000-1-uncaughtException.json',
kind: 'uncaughtException',
message: 'boom',
timestamp: '2026-01-01T00:00:00.000Z',
},
])
);
render(<CrashReportModal onClose={() => {}} />);
expect(await screen.findByText(/boom/)).toBeInTheDocument();
});
test('delete button calls ipc.crash.delete', async () => {
(ipc.crash.read as any).mockResolvedValue(
wrap([
{
filename: '1700000000000-1-uncaughtException.json',
kind: 'uncaughtException',
message: 'boom',
timestamp: '2026-01-01T00:00:00.000Z',
},
])
);
render(<CrashReportModal onClose={() => {}} />);
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(wrap([]));
render(<CrashReportModal onClose={() => {}} />);
fireEvent.click(await screen.findByText(/open dump folder/i));
expect(ipc.crash.openDir).toHaveBeenCalled();
});
});