mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 20:03:13 +08:00
6bab6fbe97
Documents the agent writes open in the workspace panel instead of another application, and local images the agent mentions show up in the conversation. Workspace preview - PDF (pdf.js with its own layout and text layer), Word (docx-preview inside a scripts-disabled sandboxed iframe) and Excel (SheetJS; .xlsx, .xlsm, .xls) open in the side panel with zoom and fit, per-file scroll/zoom/sheet memory, and a refresh when the agent rewrites the file. The engines load lazily. - Bytes come from a new GET /api/sessions/:id/workspace/raw route, with an extension allowlist, size caps, the workspace boundary and canonical-path checks. The file endpoint returns metadata and a version for documents. The client fetches with the bearer credential, so it works in Electron, LAN H5 and remote access alike. - Chat links, output cards and the change card open pdf/docx/xlsx in the workspace; documents outside the workdir still go to the system application. - Image viewer with fit, zoom and pan, and "open in system app". Chat images - Markdown images outside the workdir, at ~/, C:\ and file:// paths render, open in the viewer, and offer "open original" (pictures only). - Images returned by tools such as Read appear as thumbnails under the call. Hardening found in review - previewFsUrl escapes each path segment; a double-escaped %2e%2e used to leave /preview-fs/<session>/. - The CORS, API timing and remote-access header decorators set headers in place. Rebuilding the response buffered whole files in memory and dropped Content-Length. - The engine owns the pdf.js worker, so closing one document no longer fails the next open. - Office archives are inflated in steps to check their real sizes, not the sizes they declare. - A viewer that fails to load stays in its panel instead of taking the window down. Adds pdfjs-dist, docx-preview, xlsx (SheetJS 0.20.3 tarball) and fflate as renderer dev dependencies; Vite bundles them. Refs #1397
79 lines
3.3 KiB
TypeScript
79 lines
3.3 KiB
TypeScript
import '@testing-library/jest-dom/vitest'
|
|
import { render, screen } from '@testing-library/react'
|
|
import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest'
|
|
import { reportReactError } from '@/lib/diagnosticsCapture'
|
|
import { DocumentViewerBoundary } from './DocumentViewerBoundary'
|
|
|
|
vi.mock('@/lib/diagnosticsCapture', () => ({
|
|
reportReactError: vi.fn(),
|
|
}))
|
|
|
|
function Viewer({ crashes }: { crashes: boolean }) {
|
|
if (crashes) throw new Error('cannot draw this')
|
|
return <div>drawn</div>
|
|
}
|
|
|
|
const fallback = <div role="alert">could not be shown</div>
|
|
|
|
let consoleError: MockInstance<typeof console.error>
|
|
|
|
beforeEach(() => {
|
|
vi.mocked(reportReactError).mockClear()
|
|
// React reports a render error it has caught to the console as well.
|
|
consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
})
|
|
|
|
afterEach(() => {
|
|
consoleError.mockRestore()
|
|
})
|
|
|
|
describe('DocumentViewerBoundary', () => {
|
|
it('shows the viewer while it draws', () => {
|
|
render(<DocumentViewerBoundary resetKey="a" fallback={fallback}><Viewer crashes={false} /></DocumentViewerBoundary>)
|
|
|
|
expect(screen.getByText('drawn')).toBeInTheDocument()
|
|
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('shows the fallback in its place when the viewer throws, instead of letting it reach the window', () => {
|
|
render(<DocumentViewerBoundary resetKey="a" fallback={fallback}><Viewer crashes /></DocumentViewerBoundary>)
|
|
|
|
expect(screen.getByRole('alert')).toHaveTextContent('could not be shown')
|
|
expect(screen.queryByText('drawn')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('records the failure in the diagnostics, where it can be found', () => {
|
|
render(<DocumentViewerBoundary resetKey="a" fallback={fallback}><Viewer crashes /></DocumentViewerBoundary>)
|
|
|
|
expect(reportReactError).toHaveBeenCalledTimes(1)
|
|
expect(vi.mocked(reportReactError).mock.calls[0]![0]).toMatchObject({ message: 'cannot draw this' })
|
|
})
|
|
|
|
it('stays failed while nothing about what it shows has changed', () => {
|
|
const { rerender } = render(<DocumentViewerBoundary resetKey="a" fallback={fallback}><Viewer crashes /></DocumentViewerBoundary>)
|
|
|
|
rerender(<DocumentViewerBoundary resetKey="a" fallback={fallback}><Viewer crashes={false} /></DocumentViewerBoundary>)
|
|
|
|
expect(screen.getByRole('alert')).toBeInTheDocument()
|
|
expect(screen.queryByText('drawn')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('tries the viewer again once the key changes, which is what another document or another attempt is', () => {
|
|
const { rerender } = render(<DocumentViewerBoundary resetKey="a" fallback={fallback}><Viewer crashes /></DocumentViewerBoundary>)
|
|
|
|
rerender(<DocumentViewerBoundary resetKey="b" fallback={fallback}><Viewer crashes={false} /></DocumentViewerBoundary>)
|
|
|
|
expect(screen.getByText('drawn')).toBeInTheDocument()
|
|
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('fails again, with the fallback, when the retry crashes too', () => {
|
|
const { rerender } = render(<DocumentViewerBoundary resetKey="a" fallback={fallback}><Viewer crashes /></DocumentViewerBoundary>)
|
|
|
|
rerender(<DocumentViewerBoundary resetKey="b" fallback={fallback}><Viewer crashes /></DocumentViewerBoundary>)
|
|
|
|
expect(screen.getByRole('alert')).toBeInTheDocument()
|
|
expect(reportReactError).toHaveBeenCalledTimes(2)
|
|
})
|
|
})
|