mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Online upgrades can strand users on stale desktop UI state or malformed local persistence. This adds a deny-by-default Doctor path that resets only regenerable desktop UI state, reports protected local files with redacted metadata, and keeps protected repair as a dry-run no-op until a reviewed backup-first flow exists. Constraint: Chat transcripts, model/provider config, Skills, MCP, IM bindings, adapter sessions, OAuth tokens, plugins, and team/session records are user-owned protected state. Rejected: Automatically rewrite malformed protected JSON | unsafe without schema-specific migrations and backups. Rejected: Continue relying only on startup migrations | users need an explicit recovery action after a white screen. Confidence: high Scope-risk: moderate Directive: Keep Doctor repair deny-by-default; do not mutate protected state without an explicit reviewed backup-first manual repair flow. Tested: bun test src/server/__tests__/doctor-service.test.ts Tested: cd desktop && bun run test src/components/ErrorBoundary.test.tsx src/lib/doctorRepair.test.ts src/__tests__/diagnosticsSettings.test.tsx src/components/layout/StartupErrorView.test.tsx Tested: cd desktop && bun run lint Tested: bun run check:server Tested: bun run verify (failed only existing agent-utils coverage baseline; changed-lines coverage 97.62%) Not-tested: Packaged desktop manual Doctor click path.
50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
import { render, screen } from '@testing-library/react'
|
|
import '@testing-library/jest-dom'
|
|
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
|
|
|
|
import { useSettingsStore } from '../stores/settingsStore'
|
|
import { ErrorBoundary } from './ErrorBoundary'
|
|
import { reportReactError } from '../lib/diagnosticsCapture'
|
|
|
|
vi.mock('../lib/diagnosticsCapture', () => ({
|
|
reportReactError: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('./doctor/DoctorPanel', () => ({
|
|
DoctorPanel: ({ compact }: { compact?: boolean }) => (
|
|
<div data-testid="doctor-panel">{compact ? 'compact doctor' : 'doctor'}</div>
|
|
),
|
|
}))
|
|
|
|
function CrashingChild(): never {
|
|
throw new Error('boom')
|
|
}
|
|
|
|
describe('ErrorBoundary', () => {
|
|
let consoleErrorSpy: ReturnType<typeof vi.spyOn>
|
|
|
|
beforeEach(() => {
|
|
useSettingsStore.setState({ locale: 'en' })
|
|
vi.clearAllMocks()
|
|
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
})
|
|
|
|
afterEach(() => {
|
|
consoleErrorSpy.mockRestore()
|
|
})
|
|
|
|
it('shows retry and compact Doctor fallback when a child crashes', () => {
|
|
render(
|
|
<ErrorBoundary>
|
|
<CrashingChild />
|
|
</ErrorBoundary>,
|
|
)
|
|
|
|
expect(screen.getByText('Something went wrong.')).toBeInTheDocument()
|
|
expect(screen.getByText('The error was recorded in Diagnostics.')).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: 'Retry' })).toBeInTheDocument()
|
|
expect(screen.getByTestId('doctor-panel')).toHaveTextContent('compact doctor')
|
|
expect(reportReactError).toHaveBeenCalled()
|
|
})
|
|
})
|