feat: remove legacy OpenCode runtime
Cut product flows over to Coding/Pi and retain only the migration-owned v1 boundary. Promote supported native optional packages because electron-builder omitted pnpm transitive optional closure from the packaged ASAR.
This commit is contained in:
1 parent
61fede2b4d
commit
5a275b93a7
199 files changed
+1330
-64725
No files matched your search
@@ -1,619 +0,0 @@
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { AgentBrowserPanel } from '@/pages/Chat/AgentBrowserPanel';
|
||||
import { TitleBar } from '@/components/layout/TitleBar';
|
||||
import { deriveAgentBrowserDiagnostics } from '@/lib/agent-browser';
|
||||
import type {
|
||||
AgentBrowserCdpEvent,
|
||||
AgentBrowserSnapshot,
|
||||
} from '../../shared/agent-browser';
|
||||
|
||||
const hostApiFetchMock = vi.hoisted(() => vi.fn());
|
||||
const hostEventListeners = vi.hoisted(
|
||||
() => new Map<string, (payload: unknown) => void>(),
|
||||
);
|
||||
|
||||
vi.mock('@/lib/host-api', () => ({
|
||||
hostApiFetch: (...args: unknown[]) => hostApiFetchMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/host-events', () => ({
|
||||
subscribeHostEvent: (
|
||||
eventName: string,
|
||||
handler: (payload: unknown) => void,
|
||||
) => {
|
||||
hostEventListeners.set(eventName, handler);
|
||||
return () => {
|
||||
hostEventListeners.delete(eventName);
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
let resizeCallback: ResizeObserverCallback | null = null;
|
||||
|
||||
class MockResizeObserver {
|
||||
constructor(callback: ResizeObserverCallback) {
|
||||
resizeCallback = callback;
|
||||
}
|
||||
|
||||
observe() {}
|
||||
|
||||
unobserve() {}
|
||||
|
||||
disconnect() {}
|
||||
}
|
||||
|
||||
function snapshot(
|
||||
state: AgentBrowserSnapshot['state'] = 'closed',
|
||||
overrides: Partial<AgentBrowserSnapshot> = {},
|
||||
): AgentBrowserSnapshot {
|
||||
const active = state !== 'closed' && state !== 'closing';
|
||||
return {
|
||||
browserId: active ? 'browser-1' : null,
|
||||
projectId: 'project-1',
|
||||
projectPath: 'D:/repo',
|
||||
state,
|
||||
generation: active ? 1 : 0,
|
||||
url: active ? 'http://localhost:4173/' : '',
|
||||
title: active ? 'Student app' : '',
|
||||
visible: active,
|
||||
bounds: null,
|
||||
canGoBack: false,
|
||||
canGoForward: false,
|
||||
eventCursor: 0,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function parseBody(init?: RequestInit): Record<string, unknown> {
|
||||
return JSON.parse(String(init?.body ?? '{}')) as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function installBrowserApi(
|
||||
initial: AgentBrowserSnapshot,
|
||||
events: AgentBrowserCdpEvent[] = [],
|
||||
) {
|
||||
let current = initial;
|
||||
let eventsReturned = false;
|
||||
hostApiFetchMock.mockImplementation(async (path: string, init?: RequestInit) => {
|
||||
if (path.startsWith('/api/agent-browser/state?')) {
|
||||
return { success: true, browser: current };
|
||||
}
|
||||
if (path === '/api/agent-browser/open') {
|
||||
const body = parseBody(init);
|
||||
current = snapshot('attached', {
|
||||
url: String(body.url),
|
||||
visible: true,
|
||||
bounds: body.bounds as AgentBrowserSnapshot['bounds'],
|
||||
});
|
||||
return { success: true, browser: current };
|
||||
}
|
||||
if (path === '/api/agent-browser/present') {
|
||||
const body = parseBody(init);
|
||||
current = {
|
||||
...current,
|
||||
visible: body.visible === true,
|
||||
bounds: (body.bounds as AgentBrowserSnapshot['bounds']) ?? current.bounds,
|
||||
};
|
||||
return { success: true, browser: current };
|
||||
}
|
||||
if (path === '/api/agent-browser/diagnostics') {
|
||||
return { success: true, browser: current };
|
||||
}
|
||||
if (path === '/api/agent-browser/navigate') {
|
||||
return { success: true, browser: current };
|
||||
}
|
||||
if (path === '/api/agent-browser/close') {
|
||||
current = snapshot();
|
||||
return { success: true, browser: current };
|
||||
}
|
||||
if (path === '/api/agent-browser/cdp/events') {
|
||||
const pageEvents = eventsReturned ? [] : events;
|
||||
eventsReturned = true;
|
||||
return {
|
||||
success: true,
|
||||
page: {
|
||||
events: pageEvents,
|
||||
nextCursor: pageEvents.at(-1)?.sequence ?? current.eventCursor,
|
||||
hasMore: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected Agent Browser request: ${path}`);
|
||||
});
|
||||
}
|
||||
|
||||
describe('AgentBrowserPanel', () => {
|
||||
beforeEach(() => {
|
||||
hostEventListeners.clear();
|
||||
resizeCallback = null;
|
||||
Object.defineProperty(document, 'elementsFromPoint', {
|
||||
configurable: true,
|
||||
value: vi.fn().mockReturnValue([]),
|
||||
});
|
||||
Object.defineProperty(window, 'ResizeObserver', {
|
||||
configurable: true,
|
||||
value: MockResizeObserver,
|
||||
});
|
||||
Object.defineProperty(globalThis, 'ResizeObserver', {
|
||||
configurable: true,
|
||||
value: MockResizeObserver,
|
||||
});
|
||||
Object.defineProperty(window, 'devicePixelRatio', {
|
||||
configurable: true,
|
||||
value: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('starts collapsed and cannot open without an active project', () => {
|
||||
render(<AgentBrowserPanel projectPath={null} />);
|
||||
|
||||
expect(screen.queryByTestId('agent-browser-panel')).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '打开开发浏览器' })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('renders the collapsed toggle in the fixed titlebar layer', async () => {
|
||||
installBrowserApi(snapshot());
|
||||
render(<AgentBrowserPanel projectPath="D:/repo" />);
|
||||
|
||||
const titlebarToggle = await screen.findByTestId('agent-browser-titlebar-toggle');
|
||||
const button = titlebarToggle.querySelector('[data-testid="agent-browser-panel-open"]') as HTMLElement;
|
||||
expect(button).toHaveAttribute('aria-label', '打开开发浏览器');
|
||||
expect(button).toHaveClass('no-drag');
|
||||
expect(button).toHaveClass('bg-transparent');
|
||||
expect(button).not.toHaveClass('rounded-full');
|
||||
expect(screen.queryByTestId('agent-browser-panel')).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(button);
|
||||
expect(await screen.findByTestId('agent-browser-panel')).toBeInTheDocument();
|
||||
fireEvent.click(titlebarToggle.querySelector('[data-testid="agent-browser-panel-open"]') as HTMLElement);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('agent-browser-panel')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps the active browser indicator inside the titlebar toggle bounds', async () => {
|
||||
installBrowserApi(snapshot('attached'));
|
||||
render(<AgentBrowserPanel projectPath="D:/repo" />);
|
||||
|
||||
const titlebarToggle = await screen.findByTestId('agent-browser-titlebar-toggle');
|
||||
await waitFor(() => {
|
||||
expect(titlebarToggle.querySelector('span[aria-hidden="true"]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const indicator = titlebarToggle.querySelector('span[aria-hidden="true"]');
|
||||
expect(indicator).toHaveClass('absolute', 'right-2', 'top-2');
|
||||
expect(indicator).not.toHaveClass('right-0.5', 'top-0.5');
|
||||
});
|
||||
|
||||
it('keeps the toggle visible when mounted with the integrated titlebar', async () => {
|
||||
window.electron.platform = 'darwin';
|
||||
installBrowserApi(snapshot());
|
||||
|
||||
render(
|
||||
<>
|
||||
<TitleBar integrated />
|
||||
<AgentBrowserPanel projectPath="D:/repo" />
|
||||
</>,
|
||||
);
|
||||
|
||||
const titlebarToggle = await screen.findByTestId('agent-browser-titlebar-toggle');
|
||||
await waitFor(() => {
|
||||
expect(titlebarToggle.querySelector('[data-testid="agent-browser-panel-open"]')).toBeInTheDocument();
|
||||
});
|
||||
expect(titlebarToggle).toHaveClass('fixed', 'top-0', 'right-0', 'z-[100]', 'pointer-events-auto');
|
||||
expect(titlebarToggle).toHaveStyle({ right: '0px' });
|
||||
expect(document.documentElement.style.getPropertyValue('--agent-browser-titlebar-logo-gap')).toBe('');
|
||||
expect(screen.queryByTestId('agent-browser-panel-close')).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(titlebarToggle.querySelector('[data-testid="agent-browser-panel-open"]') as HTMLElement);
|
||||
await waitFor(() => {
|
||||
expect(document.documentElement.style.getPropertyValue('--agent-browser-titlebar-logo-gap')).toBe('4px');
|
||||
});
|
||||
});
|
||||
|
||||
it('starts the browser at the same baseline width as the other workspace columns', async () => {
|
||||
installBrowserApi(snapshot());
|
||||
render(
|
||||
<div data-testid="opencode-chat-layout">
|
||||
<div data-testid="agent-conversation-sidebar" />
|
||||
<div data-testid="opencode-chat-canvas" />
|
||||
<AgentBrowserPanel projectPath="D:/repo" />
|
||||
</div>,
|
||||
);
|
||||
|
||||
vi.spyOn(screen.getByTestId('opencode-chat-layout'), 'getBoundingClientRect')
|
||||
.mockReturnValue({ width: 960 } as DOMRect);
|
||||
fireEvent.click(screen.getByTestId('agent-browser-panel-open'));
|
||||
|
||||
expect(await screen.findByTestId('agent-browser-panel')).toHaveStyle({ width: '320px' });
|
||||
expect(screen.getByTestId('agent-browser-panel-spacer')).toHaveStyle({ width: '320px' });
|
||||
});
|
||||
|
||||
it('keeps diagnostics collapsed and expands Console or Network upward from the bottom', async () => {
|
||||
installBrowserApi(snapshot());
|
||||
render(<AgentBrowserPanel projectPath="D:/repo" />);
|
||||
|
||||
fireEvent.click(screen.getByTestId('agent-browser-panel-open'));
|
||||
const diagnostics = await screen.findByTestId('agent-browser-diagnostics');
|
||||
const consoleTab = screen.getByRole('tab', { name: /Console/ });
|
||||
|
||||
expect(screen.queryByText('尚未开启')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '开启 AI 调试' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '打开' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '清空调试记录' })).not.toBeInTheDocument();
|
||||
expect(diagnostics).toHaveAttribute('data-state', 'closed');
|
||||
expect(diagnostics).toHaveStyle({ height: '42px' });
|
||||
expect(consoleTab).toHaveAttribute('aria-expanded', 'false');
|
||||
expect(screen.queryByTestId('agent-browser-diagnostics-resize-handle')).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '展开调试面板' }));
|
||||
expect(diagnostics).toHaveAttribute('data-state', 'open');
|
||||
fireEvent.click(screen.getByRole('button', { name: '收起调试面板' }));
|
||||
expect(diagnostics).toHaveAttribute('data-state', 'closed');
|
||||
|
||||
fireEvent.click(consoleTab);
|
||||
expect(diagnostics).toHaveAttribute('data-state', 'open');
|
||||
expect(diagnostics).toHaveStyle({ height: '260px' });
|
||||
expect(consoleTab).toHaveAttribute('aria-expanded', 'true');
|
||||
expect(screen.getByTestId('agent-browser-diagnostics-resize-handle')).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: /Network/ }));
|
||||
expect(screen.getByLabelText('Network 请求')).toBeInTheDocument();
|
||||
|
||||
const resizeHandle = screen.getByTestId('agent-browser-diagnostics-resize-handle');
|
||||
fireEvent.pointerDown(resizeHandle, { button: 0, clientY: 500, pointerId: 1 });
|
||||
fireEvent.pointerMove(resizeHandle, { clientY: 420, pointerId: 1 });
|
||||
expect(diagnostics).toHaveStyle({ height: '340px' });
|
||||
fireEvent.pointerUp(resizeHandle, { pointerId: 1 });
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '收起调试面板' }));
|
||||
expect(diagnostics).toHaveAttribute('data-state', 'closed');
|
||||
expect(diagnostics).toHaveStyle({ height: '42px' });
|
||||
});
|
||||
|
||||
it('keeps bounded placeholders for oversized Console and Network events', () => {
|
||||
const diagnostics = deriveAgentBrowserDiagnostics([
|
||||
{
|
||||
sequence: 1,
|
||||
generation: 1,
|
||||
timestamp: 1_000,
|
||||
method: 'Runtime.consoleAPICalled',
|
||||
payload: {
|
||||
kind: 'payload',
|
||||
handle: 'console-large',
|
||||
byteLength: 70_000,
|
||||
contentType: 'application/json',
|
||||
expiresAt: '2026-07-28T00:00:00.000Z',
|
||||
},
|
||||
},
|
||||
{
|
||||
sequence: 2,
|
||||
generation: 1,
|
||||
timestamp: 1_010,
|
||||
method: 'Network.requestWillBeSent',
|
||||
payload: {
|
||||
kind: 'payload',
|
||||
handle: 'network-large',
|
||||
byteLength: 80_000,
|
||||
contentType: 'application/json',
|
||||
expiresAt: '2026-07-28T00:00:00.000Z',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(diagnostics.console[0]?.text).toContain('70000 字节');
|
||||
expect(diagnostics.network[0]?.url).toContain('80000 字节');
|
||||
});
|
||||
|
||||
it('keeps child-target Network requests separate when request ids collide', () => {
|
||||
const diagnostics = deriveAgentBrowserDiagnostics([
|
||||
{
|
||||
sequence: 1,
|
||||
generation: 1,
|
||||
timestamp: 1_000,
|
||||
method: 'Network.requestWillBeSent',
|
||||
params: {
|
||||
requestId: 'shared-id',
|
||||
request: { method: 'GET', url: 'http://localhost/root' },
|
||||
},
|
||||
},
|
||||
{
|
||||
sequence: 2,
|
||||
generation: 1,
|
||||
timestamp: 1_001,
|
||||
method: 'Network.requestWillBeSent',
|
||||
sessionRef: 'worker-1',
|
||||
params: {
|
||||
requestId: 'shared-id',
|
||||
request: { method: 'POST', url: 'http://localhost/worker' },
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(diagnostics.network).toHaveLength(2);
|
||||
expect(diagnostics.network.map((entry) => entry.url)).toEqual([
|
||||
'http://localhost/root',
|
||||
'http://localhost/worker',
|
||||
]);
|
||||
});
|
||||
|
||||
it('opens a local URL with viewport bounds expressed in DIP', async () => {
|
||||
installBrowserApi(snapshot());
|
||||
render(<AgentBrowserPanel projectPath="D:/repo" />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开开发浏览器' }));
|
||||
const viewport = await screen.findByTestId('agent-browser-viewport');
|
||||
vi.spyOn(viewport, 'getBoundingClientRect').mockReturnValue({
|
||||
x: 120,
|
||||
y: 80,
|
||||
left: 120,
|
||||
top: 80,
|
||||
right: 540,
|
||||
bottom: 400,
|
||||
width: 420,
|
||||
height: 320,
|
||||
toJSON: () => ({}),
|
||||
});
|
||||
|
||||
fireEvent.change(screen.getByRole('textbox', { name: '网页地址' }), {
|
||||
target: { value: 'localhost:4173' },
|
||||
});
|
||||
fireEvent.submit(screen.getByRole('textbox', { name: '网页地址' }).closest('form') as HTMLFormElement);
|
||||
|
||||
await waitFor(() => {
|
||||
const call = hostApiFetchMock.mock.calls.find(
|
||||
([path]) => path === '/api/agent-browser/open',
|
||||
);
|
||||
expect(call).toBeDefined();
|
||||
expect(parseBody(call?.[1] as RequestInit)).toEqual({
|
||||
project_path: 'D:/repo',
|
||||
url: 'http://localhost:4173',
|
||||
visible: true,
|
||||
bounds: { x: 120, y: 80, width: 420, height: 320 },
|
||||
});
|
||||
});
|
||||
expect(window.devicePixelRatio).toBe(2);
|
||||
});
|
||||
|
||||
it('does not invent a URL when the address field is empty', async () => {
|
||||
installBrowserApi(snapshot());
|
||||
render(<AgentBrowserPanel projectPath="D:/repo" />);
|
||||
|
||||
fireEvent.click(screen.getByTestId('agent-browser-panel-open'));
|
||||
const addressInput = screen.getByRole('textbox', { name: '网页地址' });
|
||||
expect(addressInput).toHaveValue('');
|
||||
fireEvent.submit(addressInput.closest('form') as HTMLFormElement);
|
||||
|
||||
await new Promise((resolve) => window.setTimeout(resolve, 0));
|
||||
expect(hostApiFetchMock.mock.calls.some(([path]) => path === '/api/agent-browser/open')).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps the panel collapsed when the agent opens the shared browser', async () => {
|
||||
installBrowserApi(snapshot('attached'));
|
||||
render(<AgentBrowserPanel projectId="project-1" projectPath="D:/repo" />);
|
||||
|
||||
expect(screen.queryByTestId('agent-browser-panel')).not.toBeInTheDocument();
|
||||
act(() => {
|
||||
hostEventListeners.get('agent-browser:show')?.(snapshot('attached'));
|
||||
});
|
||||
|
||||
expect(screen.queryByTestId('agent-browser-panel')).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('agent-browser-panel-open')).toHaveAttribute(
|
||||
'title',
|
||||
'开发浏览器已收起,AI 调试已暂停',
|
||||
);
|
||||
fireEvent.click(screen.getByTestId('agent-browser-panel-open'));
|
||||
expect(await screen.findByTestId('agent-browser-panel')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('ignores a delayed show event from the previous project', () => {
|
||||
installBrowserApi(snapshot());
|
||||
render(<AgentBrowserPanel projectId="project-2" projectPath="D:/repo-2" />);
|
||||
|
||||
act(() => {
|
||||
hostEventListeners.get('agent-browser:show')?.(
|
||||
snapshot('attached', {
|
||||
projectId: 'project-1',
|
||||
projectPath: 'D:/repo-1',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect(screen.queryByTestId('agent-browser-panel')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('restores an already active shared browser without expanding the panel', async () => {
|
||||
installBrowserApi(snapshot('attached'));
|
||||
render(<AgentBrowserPanel projectId="project-1" projectPath="D:/repo" />);
|
||||
|
||||
expect(await screen.findByTestId('agent-browser-panel-open')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('agent-browser-panel')).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByTestId('agent-browser-panel-open'));
|
||||
expect(await screen.findByTestId('agent-browser-panel')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('updates native view bounds and hides it when collapsed', async () => {
|
||||
installBrowserApi(snapshot('attached'));
|
||||
render(<AgentBrowserPanel projectPath="D:/repo" />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开开发浏览器' }));
|
||||
const viewport = screen.getByTestId('agent-browser-viewport');
|
||||
vi.spyOn(viewport, 'getBoundingClientRect').mockReturnValue({
|
||||
x: 240,
|
||||
y: 96,
|
||||
left: 240,
|
||||
top: 96,
|
||||
right: 640,
|
||||
bottom: 396,
|
||||
width: 400,
|
||||
height: 300,
|
||||
toJSON: () => ({}),
|
||||
});
|
||||
resizeCallback?.([], {} as ResizeObserver);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(hostApiFetchMock.mock.calls.some(([path, init]) => (
|
||||
path === '/api/agent-browser/present'
|
||||
&& parseBody(init as RequestInit).visible === true
|
||||
&& JSON.stringify(parseBody(init as RequestInit).bounds)
|
||||
=== JSON.stringify({ x: 240, y: 96, width: 400, height: 300 })
|
||||
))).toBe(true);
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '收起开发浏览器' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(hostApiFetchMock.mock.calls.some(([path, init]) => (
|
||||
path === '/api/agent-browser/present'
|
||||
&& parseBody(init as RequestInit).visible === false
|
||||
))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('resizes the panel from its left edge and auto-collapses below the threshold', async () => {
|
||||
installBrowserApi(snapshot('attached'));
|
||||
render(<AgentBrowserPanel projectPath="D:/repo" />);
|
||||
|
||||
fireEvent.click(screen.getByTestId('agent-browser-panel-open'));
|
||||
const panel = await screen.findByTestId('agent-browser-panel');
|
||||
const handle = screen.getByTestId('agent-browser-panel-resize-handle');
|
||||
const panelRect = vi.spyOn(panel, 'getBoundingClientRect');
|
||||
panelRect.mockReturnValue({ width: 440 } as DOMRect);
|
||||
vi.spyOn(panel.parentElement as HTMLElement, 'getBoundingClientRect').mockReturnValue({
|
||||
width: 1200,
|
||||
} as DOMRect);
|
||||
|
||||
fireEvent.pointerDown(handle, {
|
||||
button: 0,
|
||||
clientX: 600,
|
||||
pointerId: 1,
|
||||
});
|
||||
fireEvent.pointerMove(handle, {
|
||||
clientX: 440,
|
||||
pointerId: 1,
|
||||
});
|
||||
expect(panel).toHaveStyle({ width: '600px' });
|
||||
|
||||
panelRect.mockReturnValue({ width: 600 } as DOMRect);
|
||||
fireEvent.pointerDown(handle, {
|
||||
button: 0,
|
||||
clientX: 600,
|
||||
pointerId: 2,
|
||||
});
|
||||
fireEvent.pointerMove(handle, {
|
||||
clientX: 950,
|
||||
pointerId: 2,
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('agent-browser-panel')).toHaveAttribute('data-state', 'closed');
|
||||
await waitFor(() => expect(screen.queryByTestId('agent-browser-panel')).not.toBeInTheDocument());
|
||||
expect(screen.getByTestId('agent-browser-panel-open')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('hides the native view while a renderer modal is open', async () => {
|
||||
installBrowserApi(snapshot('attached'));
|
||||
render(<AgentBrowserPanel projectPath="D:/repo" />);
|
||||
|
||||
fireEvent.click(screen.getByTestId('agent-browser-panel-open'));
|
||||
const viewport = await screen.findByTestId('agent-browser-viewport');
|
||||
vi.spyOn(viewport, 'getBoundingClientRect').mockReturnValue({
|
||||
x: 240,
|
||||
y: 96,
|
||||
left: 240,
|
||||
top: 96,
|
||||
right: 640,
|
||||
bottom: 396,
|
||||
width: 400,
|
||||
height: 300,
|
||||
toJSON: () => ({}),
|
||||
});
|
||||
resizeCallback?.([], {} as ResizeObserver);
|
||||
await waitFor(() => {
|
||||
expect(hostApiFetchMock.mock.calls.some(([path, init]) => (
|
||||
path === '/api/agent-browser/present'
|
||||
&& parseBody(init as RequestInit).visible === true
|
||||
))).toBe(true);
|
||||
});
|
||||
const beforeModal = hostApiFetchMock.mock.calls.length;
|
||||
const dialog = document.createElement('div');
|
||||
dialog.setAttribute('role', 'dialog');
|
||||
dialog.setAttribute('data-state', 'open');
|
||||
act(() => document.body.append(dialog));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(hostApiFetchMock.mock.calls.slice(beforeModal).some(([path, init]) => (
|
||||
path === '/api/agent-browser/present'
|
||||
&& parseBody(init as RequestInit).visible === false
|
||||
))).toBe(true);
|
||||
});
|
||||
const beforeClose = hostApiFetchMock.mock.calls.length;
|
||||
act(() => dialog.remove());
|
||||
|
||||
await waitFor(() => {
|
||||
expect(hostApiFetchMock.mock.calls.slice(beforeClose).some(([path, init]) => (
|
||||
path === '/api/agent-browser/present'
|
||||
&& parseBody(init as RequestInit).visible === true
|
||||
))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('derives Console and Network panels from the same CDP event page', async () => {
|
||||
installBrowserApi(snapshot('attached'), [
|
||||
{
|
||||
sequence: 1,
|
||||
generation: 1,
|
||||
timestamp: 1_000,
|
||||
method: 'Runtime.consoleAPICalled',
|
||||
params: {
|
||||
type: 'log',
|
||||
args: [{ type: 'string', value: 'ready' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
sequence: 2,
|
||||
generation: 1,
|
||||
timestamp: 1_010,
|
||||
method: 'Network.requestWillBeSent',
|
||||
params: {
|
||||
requestId: 'req-1',
|
||||
type: 'Fetch',
|
||||
request: { method: 'GET', url: 'http://localhost:4173/api/data' },
|
||||
},
|
||||
},
|
||||
{
|
||||
sequence: 3,
|
||||
generation: 1,
|
||||
timestamp: 1_030,
|
||||
method: 'Network.responseReceived',
|
||||
params: {
|
||||
requestId: 'req-1',
|
||||
type: 'Fetch',
|
||||
response: {
|
||||
url: 'http://localhost:4173/api/data',
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
mimeType: 'application/json',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
sequence: 4,
|
||||
generation: 1,
|
||||
timestamp: 1_060,
|
||||
method: 'Network.loadingFinished',
|
||||
params: { requestId: 'req-1', encodedDataLength: 42 },
|
||||
},
|
||||
]);
|
||||
render(<AgentBrowserPanel projectPath="D:/repo" />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开开发浏览器' }));
|
||||
fireEvent.click(screen.getByRole('tab', { name: /Console/ }));
|
||||
expect(await screen.findByText('ready')).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: /Network/ }));
|
||||
expect(await screen.findByText('http://localhost:4173/api/data')).toBeInTheDocument();
|
||||
expect(screen.getByText('200')).toBeInTheDocument();
|
||||
expect(screen.getByText('50ms')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user