fix: contain unbroken dialog failure output (#13531)

This commit is contained in:
Brennan Benson
2026-08-10 01:24:54 -07:00
committed by GitHub
parent 131010277c
commit 9060a88dde
4 changed files with 140 additions and 11 deletions
@@ -0,0 +1,84 @@
// @vitest-environment happy-dom
import type { ReactNode } from 'react'
import { cleanup, render, waitFor } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { CrashReportRecord } from '../../../../shared/crash-reporting'
import { CrashReportDialogSurface } from './CrashReportDialogSurface'
const viewer = vi.fn(async () => null)
vi.mock('./use-crash-report-copy', () => ({
useCrashReportCopy: () => vi.fn(async () => {})
}))
vi.mock('@/components/ui/dialog', () => ({
Dialog: ({ open, children }: { open: boolean; children?: ReactNode }) =>
open ? <div>{children}</div> : null,
DialogContent: ({ className, children }: { className?: string; children?: ReactNode }) => (
<div role="dialog" className={className}>
{children}
</div>
),
DialogDescription: ({ children }: { children?: ReactNode }) => <p>{children}</p>,
DialogFooter: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
DialogHeader: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
DialogTitle: ({ children }: { children?: ReactNode }) => <h2>{children}</h2>
}))
function crashReport(error: string): CrashReportRecord {
return {
id: 'crash-1',
createdAt: '2026-08-10T00:00:00.000Z',
status: 'pending',
source: 'renderer',
processType: 'renderer',
reason: 'crashed',
exitCode: 5,
appVersion: '1.0.0',
platform: 'darwin',
osRelease: 'test',
arch: 'arm64',
electronVersion: '41',
chromeVersion: '141',
details: { error }
}
}
beforeEach(() => {
viewer.mockClear()
Object.defineProperty(window, 'api', {
configurable: true,
value: { gh: { viewer } }
})
})
afterEach(() => cleanup())
describe('CrashReportDialogSurface overflow containment', () => {
it('keeps unbroken diagnostic output inside the dialog grid', async () => {
const unbrokenError = 'A'.repeat(1000)
const { container } = render(
<CrashReportDialogSurface
open
report={crashReport(unbrokenError)}
loading={false}
onOpenChange={() => {}}
onReportChange={() => {}}
/>
)
await waitFor(() => expect(viewer).toHaveBeenCalledOnce())
const dialog = container.querySelector('[role="dialog"]')
const output = dialog?.querySelector('pre')
expect(output?.textContent).toContain(unbrokenError)
expect(output?.className).toContain('[overflow-wrap:anywhere]')
expect(output?.className).not.toContain('break-words')
const gridChild = Array.from(dialog?.children ?? []).find((child) =>
child.contains(output ?? null)
)
expect(gridChild?.className).toContain('min-w-0')
expect(output?.parentElement?.className).toContain('min-w-0')
})
})
@@ -241,7 +241,7 @@ export function CrashReportDialogSurface({
<DialogDescription className="text-xs">{getDialogDescription(report)}</DialogDescription>
</DialogHeader>
<div className="space-y-3">
<div className="min-w-0 space-y-3">
{report ? (
<>
<div className="rounded-md border border-border/70 bg-muted/30 p-3 text-xs">
@@ -252,14 +252,14 @@ export function CrashReportDialogSurface({
{report.appVersion}
</div>
</div>
<div className="space-y-1.5">
<div className="min-w-0 space-y-1.5">
<div className="text-[11px] font-medium text-muted-foreground">
{translate(
'auto.components.crash.report.CrashReportDialog.6d3ebe216a',
'Diagnostic text'
)}
</div>
<pre className="max-h-44 overflow-auto whitespace-pre-wrap break-words rounded-md border border-border bg-muted/20 p-3 font-mono text-[11px] leading-5 text-muted-foreground scrollbar-sleek">
<pre className="max-h-44 overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] rounded-md border border-border bg-muted/20 p-3 font-mono text-[11px] leading-5 text-muted-foreground scrollbar-sleek">
{diagnosticText}
</pre>
</div>
@@ -73,8 +73,10 @@ vi.mock('@/components/ui/collapsible', () => ({
CollapsibleTrigger: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
// Tagged so tests can prove WHERE content sits: this mock renders it whether
// or not the disclosure is open, so presence in the DOM alone proves nothing.
CollapsibleContent: ({ children }: { children?: ReactNode }) => (
<div data-collapsible-content>{children}</div>
CollapsibleContent: ({ className, children }: { className?: string; children?: ReactNode }) => (
<div className={className} data-collapsible-content>
{children}
</div>
)
}))
@@ -386,6 +388,34 @@ describe('SkillFreshnessUpdateDialog', () => {
)
})
it('contains unbroken failure output inside the dialog grid', async () => {
const unbrokenOutput = 'A'.repeat(1000)
const unbrokenMessage = 'B'.repeat(1000)
await renderDialog()
await openViaRequest()
await emitRun({
state: 'error',
names: ['orca-cli'],
failedNames: ['orca-cli'],
finishedAt: 3,
output: unbrokenOutput,
message: unbrokenMessage
})
const output = container?.querySelector('pre')
expect(output?.textContent).toContain(unbrokenOutput)
expect(output?.className).toContain('[overflow-wrap:anywhere]')
expect(output?.className).not.toContain('break-words')
expect(output?.parentElement?.className).toContain('min-w-0')
expect(output?.closest('[data-collapsible-open]')?.className).toContain('min-w-0')
const message = Array.from(container?.querySelectorAll('p') ?? []).find(
(candidate) => candidate.textContent === unbrokenMessage
)
expect(message?.className).toContain('[overflow-wrap:anywhere]')
expect(message?.parentElement?.className).toContain('min-w-0')
})
it('shows the up-to-date state once every installation is current', async () => {
mocks.inventory = {
schemaVersion: 1,
@@ -673,6 +703,21 @@ describe('SkillFreshnessUpdateDialog', () => {
expect(container?.querySelector('[data-skill-row="orca-cli"]')).toBeNull()
expect(findButton('Update 1 skill')).toBeUndefined()
})
it('wraps an unbroken scan error inside the dialog grid', async () => {
const unbrokenError = 'C'.repeat(1000)
mocks.inventory = null
mocks.error = unbrokenError
await renderDialog()
await openViaRequest()
const error = Array.from(container?.querySelectorAll('p') ?? []).find(
(candidate) => candidate.textContent === unbrokenError
)
expect(error?.className).toContain('min-w-0')
expect(error?.className).toContain('[overflow-wrap:anywhere]')
})
it('shows incomplete plugin coverage without presenting a fabricated skill copy', async () => {
mocks.inventory = {
schemaVersion: 1,
@@ -41,7 +41,7 @@ function RunLog({ output }: { output: string }): React.JSX.Element | null {
return null
}
return (
<Collapsible>
<Collapsible className="min-w-0">
<CollapsibleTrigger asChild>
<Button
type="button"
@@ -53,9 +53,9 @@ function RunLog({ output }: { output: string }): React.JSX.Element | null {
{translate('auto.components.skills.SkillFreshnessUpdateDialog.showLog', 'Show log')}
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="mt-1">
<CollapsibleContent className="mt-1 min-w-0">
{/* Displayed verbatim, never parsed — `skills update` has no --json. */}
<pre className="scrollbar-sleek max-h-40 overflow-auto whitespace-pre-wrap break-words rounded-md border border-border bg-muted px-3 py-2.5 font-mono text-[11px] leading-relaxed text-muted-foreground">
<pre className="scrollbar-sleek max-h-40 overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] rounded-md border border-border bg-muted px-3 py-2.5 font-mono text-[11px] leading-relaxed text-muted-foreground">
{output.trim()}
</pre>
</CollapsibleContent>
@@ -278,7 +278,7 @@ export function SkillFreshnessUpdateDialog(): React.JSX.Element {
</DialogHeader>
{state.error && !isRunning && !showResult ? (
<p className="text-xs text-destructive">{state.error}</p>
<p className="min-w-0 [overflow-wrap:anywhere] text-xs text-destructive">{state.error}</p>
) : (
headline
)}
@@ -323,14 +323,14 @@ export function SkillFreshnessUpdateDialog(): React.JSX.Element {
) : null}
{run.state === 'error' ? (
<div className="space-y-2.5 rounded-md border border-destructive/35 bg-destructive/10 p-3">
<div className="min-w-0 space-y-2.5 rounded-md border border-destructive/35 bg-destructive/10 p-3">
<p className="text-[13px] font-medium text-foreground">
{translate(
'auto.components.skills.SkillFreshnessUpdateDialog.errorTitle',
"The update didn't finish"
)}
</p>
<p className="break-words font-mono text-[11px] leading-relaxed text-muted-foreground">
<p className="[overflow-wrap:anywhere] font-mono text-[11px] leading-relaxed text-muted-foreground">
{run.message}
</p>
<div className="flex flex-wrap gap-1.5">