Files
research-workbench/frontend/src/test/BrowserPanel.test.tsx
T
Ken b895a1c94b feat: wire RightPanel with Browser + Artifacts, add A2UI rendering to chat
- RightPanel: replace placeholder content with BrowserPanel + ArtifactsPanel
  components, routing sessionId to whichever tab is active
- ChatPanel: import A2UIRenderer + parseA2UIBlocks; render A2UI blocks between
  markdown and tool-call cards in assistant messages; onAction sends
  '[action] {json}' back via onSend
- ChatPanel: add parseVisualBlocks() helper, VisualBlock wrapper component
  (loads runtime.html via MCPAppRenderer, view-source toggle, pop-out),
  rendered after A2UI blocks in assistant messages
- MCPAppRenderer: on ui/initialize route runtime='visual-artifact' data to a
  render dispatch instead of tool/input; add render forwarding case
- Fix pre-existing TS6133 unused-import errors in A2UIRenderer + BrowserPanel test
- All 216 tests pass; npm run build succeeds
2026-05-26 20:43:28 +00:00

171 lines
6.7 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { render, screen } from '@testing-library/react'
import { BrowserPanel } from '../components/BrowserPanel'
describe('BrowserPanel', () => {
const originalLocation = window.location
beforeEach(() => {
// Reset location before each test
Object.defineProperty(window, 'location', {
configurable: true,
writable: true,
value: { hostname: 'localhost' },
})
})
afterEach(() => {
Object.defineProperty(window, 'location', {
configurable: true,
writable: true,
value: originalLocation,
})
})
describe('placeholder (no sessionId)', () => {
it('renders placeholder text when sessionId is null', () => {
render(<BrowserPanel sessionId={null} />)
expect(screen.getByText('Select a session to view the browser')).toBeTruthy()
})
it('does not render an iframe when sessionId is null', () => {
const { container } = render(<BrowserPanel sessionId={null} />)
expect(container.querySelector('iframe')).toBeNull()
})
it('placeholder div has padding 2rem', () => {
const { container } = render(<BrowserPanel sessionId={null} />)
const div = container.firstElementChild as HTMLElement
expect(div.style.padding).toBe('2rem')
})
it('placeholder div text has color #6a6a8a', () => {
const { container } = render(<BrowserPanel sessionId={null} />)
const div = container.firstElementChild as HTMLElement
// jsdom normalizes hex colors to rgb() — #6a6a8a = rgb(106, 106, 138)
expect(div.style.color).toBe('rgb(106, 106, 138)')
})
it('placeholder div text is centered', () => {
const { container } = render(<BrowserPanel sessionId={null} />)
const div = container.firstElementChild as HTMLElement
expect(div.style.textAlign).toBe('center')
})
})
describe('iframe (with sessionId)', () => {
it('renders an iframe when sessionId is provided', () => {
const { container } = render(<BrowserPanel sessionId="session-abc" />)
expect(container.querySelector('iframe')).toBeTruthy()
})
it('does not render placeholder text when sessionId is provided', () => {
render(<BrowserPanel sessionId="session-abc" />)
expect(screen.queryByText('Select a session to view the browser')).toBeNull()
})
it('iframe has title "Browser - noVNC"', () => {
const { container } = render(<BrowserPanel sessionId="session-abc" />)
const iframe = container.querySelector('iframe') as HTMLIFrameElement
expect(iframe.title).toBe('Browser - noVNC')
})
it('iframe has width 100%', () => {
const { container } = render(<BrowserPanel sessionId="session-abc" />)
const iframe = container.querySelector('iframe') as HTMLIFrameElement
expect(iframe.style.width).toBe('100%')
})
it('iframe has height 100%', () => {
const { container } = render(<BrowserPanel sessionId="session-abc" />)
const iframe = container.querySelector('iframe') as HTMLIFrameElement
expect(iframe.style.height).toBe('100%')
})
it('iframe has no border', () => {
const { container } = render(<BrowserPanel sessionId="session-abc" />)
const iframe = container.querySelector('iframe') as HTMLIFrameElement
// jsdom expands border shorthand — check borderStyle which carries the 'none' value
expect(iframe.style.borderStyle).toBe('none')
})
it('iframe has dark background color #0a0a1a', () => {
const { container } = render(<BrowserPanel sessionId="session-abc" />)
const iframe = container.querySelector('iframe') as HTMLIFrameElement
// jsdom normalizes hex colors to rgb() — #0a0a1a = rgb(10, 10, 26)
expect(iframe.style.background).toBe('rgb(10, 10, 26)')
})
it('iframe has clipboard allow attribute', () => {
const { container } = render(<BrowserPanel sessionId="session-abc" />)
const iframe = container.querySelector('iframe') as HTMLIFrameElement
expect(iframe.getAttribute('allow')).toBe('clipboard-read; clipboard-write')
})
})
describe('getVncUrl() — URL detection', () => {
it('returns cloudflared HTTPS URL when hostname is research.ampbox.io', () => {
Object.defineProperty(window, 'location', {
configurable: true,
writable: true,
value: { hostname: 'research.ampbox.io' },
})
const { container } = render(<BrowserPanel sessionId="session-1" />)
const iframe = container.querySelector('iframe') as HTMLIFrameElement
expect(iframe.src).toBe(
'https://vnc.ampbox.io/vnc.html?autoconnect=true&resize=remote&quality=6&compression=2'
)
})
it('returns localhost http URL with port 6080 when hostname is localhost', () => {
Object.defineProperty(window, 'location', {
configurable: true,
writable: true,
value: { hostname: 'localhost' },
})
const { container } = render(<BrowserPanel sessionId="session-1" />)
const iframe = container.querySelector('iframe') as HTMLIFrameElement
expect(iframe.src).toBe(
'http://localhost:6080/vnc.html?autoconnect=true&resize=remote&quality=6&compression=2'
)
})
it('returns http URL with other hostname when not research.ampbox.io', () => {
Object.defineProperty(window, 'location', {
configurable: true,
writable: true,
value: { hostname: '192.168.1.100' },
})
const { container } = render(<BrowserPanel sessionId="session-1" />)
const iframe = container.querySelector('iframe') as HTMLIFrameElement
expect(iframe.src).toBe(
'http://192.168.1.100:6080/vnc.html?autoconnect=true&resize=remote&quality=6&compression=2'
)
})
it('URL includes autoconnect=true query param', () => {
const { container } = render(<BrowserPanel sessionId="session-1" />)
const iframe = container.querySelector('iframe') as HTMLIFrameElement
expect(iframe.src).toContain('autoconnect=true')
})
it('URL includes resize=remote query param', () => {
const { container } = render(<BrowserPanel sessionId="session-1" />)
const iframe = container.querySelector('iframe') as HTMLIFrameElement
expect(iframe.src).toContain('resize=remote')
})
it('URL includes quality=6 query param', () => {
const { container } = render(<BrowserPanel sessionId="session-1" />)
const iframe = container.querySelector('iframe') as HTMLIFrameElement
expect(iframe.src).toContain('quality=6')
})
it('URL includes compression=2 query param', () => {
const { container } = render(<BrowserPanel sessionId="session-1" />)
const iframe = container.querySelector('iframe') as HTMLIFrameElement
expect(iframe.src).toContain('compression=2')
})
})
})