feat: integrate pixel teacher presence and resilient classroom preview
This commit is contained in:
1 parent
12d800ec51
commit
7951cca700
60 files changed
+3313
-180
No files matched your search
@@ -82,6 +82,7 @@ describe('AgentBrowserPanel', () => {
|
||||
width: 400, height: 600, toJSON: () => ({}),
|
||||
} as DOMRect);
|
||||
hostApiFetchMock.mockImplementation(async (path: string, init?: RequestInit) => {
|
||||
if (path === '/api/agent-browser/ensure-work') return { success: true, status: 'ready', browser: snapshot() };
|
||||
if (path.startsWith('/api/agent-browser/state?')) {
|
||||
return { success: true, browser: snapshot() };
|
||||
}
|
||||
@@ -114,6 +115,95 @@ describe('AgentBrowserPanel', () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('offers login recovery instead of repeatedly trying to start the page', async () => {
|
||||
const original = hostApiFetchMock.getMockImplementation()!;
|
||||
hostApiFetchMock.mockImplementation(async (path: string, init?: RequestInit) => path === '/api/agent-browser/ensure-work'
|
||||
? { status: 'failed', errorCode: 'CODING_PROVIDER_AUTH_REQUIRED', message: '登录已失效' } : original(path, init));
|
||||
const onRecovery = vi.fn();
|
||||
render(<AgentBrowserPanel projectId="project-a" embedded open onOpenChange={vi.fn()} onRecovery={onRecovery} />);
|
||||
fireEvent.click(await screen.findByRole('button', { name: '登录并继续' }));
|
||||
expect(onRecovery).toHaveBeenCalledWith('login', true);
|
||||
expect(hostApiFetchMock.mock.calls.filter(([path]) => path === '/api/agent-browser/ensure-work')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('shows preparation and a retryable failure without covering it with the native page', async () => {
|
||||
const original = hostApiFetchMock.getMockImplementation()!;
|
||||
hostApiFetchMock.mockImplementation(async (path: string, init?: RequestInit) => {
|
||||
if (path === '/api/agent-browser/ensure-work') return { status: 'failed', message: '当前项目还没有可打开的页面。' };
|
||||
return original(path, init);
|
||||
});
|
||||
const onOpenChange = vi.fn();
|
||||
render(<AgentBrowserPanel projectId="project-a" conversationId="c1" embedded open onOpenChange={onOpenChange} />);
|
||||
await screen.findByText('当前项目还没有可打开的页面。');
|
||||
expect(latestPresentation()).toMatchObject({ visible: false });
|
||||
const first = hostApiFetchMock.mock.calls.find(([path]) => path === '/api/agent-browser/ensure-work');
|
||||
fireEvent.click(screen.getByRole('button', { name: '帮我检查并打开' }));
|
||||
await waitFor(() => expect(hostApiFetchMock.mock.calls.filter(([path]) => path === '/api/agent-browser/ensure-work')).toHaveLength(2));
|
||||
const last = hostApiFetchMock.mock.calls.filter(([path]) => path === '/api/agent-browser/ensure-work').at(-1);
|
||||
expect(body(first![1]).request_id).not.toBe(body(last![1]).request_id);
|
||||
fireEvent.click(screen.getByRole('button', { name: '回到操作对话' }));
|
||||
expect(onOpenChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('ignores a stale closed snapshot arriving after foreground restoration', async () => {
|
||||
let resolveState!: (value: unknown) => void;
|
||||
const original = hostApiFetchMock.getMockImplementation()!;
|
||||
hostApiFetchMock.mockImplementation(async (path: string, init?: RequestInit) => {
|
||||
if (path.startsWith('/api/agent-browser/state?')) return new Promise((resolve) => { resolveState = resolve; });
|
||||
if (path === '/api/agent-browser/ensure-work') return { status: 'ready', browser: snapshot({ generation: 2 }) };
|
||||
if (path === '/api/agent-browser/present') return { success: true, browser: snapshot({ generation: 2, visible: body(init).visible === true }) };
|
||||
return original(path, init);
|
||||
});
|
||||
render(<AgentBrowserPanel projectId="project-a" embedded open onOpenChange={vi.fn()} />);
|
||||
await waitFor(() => expect(latestPresentation()).toMatchObject({ visible: true }));
|
||||
await act(async () => resolveState({ success: true, browser: snapshot({ generation: 1, browserId: null, state: 'closed', url: '' }) }));
|
||||
expect(screen.queryByText('正在打开你的作品…')).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '刷新网页' })).toBeEnabled();
|
||||
expect(latestPresentation()).toMatchObject({ visible: true });
|
||||
});
|
||||
|
||||
it('recovers a closed view without waiting for another focus event', async () => {
|
||||
render(<AgentBrowserPanel projectId="project-a" embedded open onOpenChange={vi.fn()} />);
|
||||
await waitFor(() => expect(latestPresentation()).toMatchObject({ visible: true }));
|
||||
act(() => hostEventListeners.get('agent-browser:state')?.(snapshot({ browserId: null, state: 'closed', url: '' })));
|
||||
await waitFor(() => expect(hostApiFetchMock.mock.calls.filter(([path]) => path === '/api/agent-browser/ensure-work')).toHaveLength(2));
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '刷新网页' })).toBeEnabled());
|
||||
});
|
||||
|
||||
it('recovers a presentation-close race and removes the internal error', async () => {
|
||||
const original = hostApiFetchMock.getMockImplementation()!;
|
||||
let failures = 0;
|
||||
hostApiFetchMock.mockImplementation(async (path: string, init?: RequestInit) => {
|
||||
if (path === '/api/agent-browser/present' && body(init).visible && failures++ === 0) throw new Error('开发浏览器已关闭。');
|
||||
return original(path, init);
|
||||
});
|
||||
render(<AgentBrowserPanel projectId="project-a" embedded open onOpenChange={vi.fn()} />);
|
||||
await waitFor(() => expect(failures).toBe(2));
|
||||
expect(screen.queryByText('开发浏览器已关闭。')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('bounds repeated presentation failures and offers a working retry', async () => {
|
||||
const original = hostApiFetchMock.getMockImplementation()!;
|
||||
let unavailable = true;
|
||||
let failures = 0;
|
||||
hostApiFetchMock.mockImplementation(async (path: string, init?: RequestInit) => {
|
||||
if (path === '/api/agent-browser/present' && body(init).visible && unavailable) {
|
||||
failures++;
|
||||
throw new Error('开发浏览器已关闭。');
|
||||
}
|
||||
return original(path, init);
|
||||
});
|
||||
render(<AgentBrowserPanel projectId="project-a" embedded open onOpenChange={vi.fn()} />);
|
||||
const retry = await screen.findByRole('button', { name: '重新打开作品' });
|
||||
expect(failures).toBe(3);
|
||||
expect(screen.queryByText('开发浏览器已关闭。')).not.toBeInTheDocument();
|
||||
unavailable = false;
|
||||
fireEvent.click(retry);
|
||||
await waitFor(() => expect(latestPresentation()).toMatchObject({ visible: true }));
|
||||
await waitFor(() => expect(screen.queryByText('作品画面暂时没能显示')).not.toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('does no browser API work while closed and presents an agent-opened snapshot', async () => {
|
||||
render(<Harness />);
|
||||
|
||||
@@ -185,6 +275,10 @@ describe('AgentBrowserPanel', () => {
|
||||
await waitFor(() => expect(latestPresentation()).toMatchObject({ visible: true }));
|
||||
expect(screen.getByRole('textbox', { name: '网页地址' })).toHaveValue('http://127.0.0.1:4173/');
|
||||
expect(screen.getByRole('button', { name: '刷新网页' })).toBeEnabled();
|
||||
expect(screen.queryByTestId('agent-browser-diagnostics')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('tab', { name: /Console|Network/ })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '展开调试面板' })).not.toBeInTheDocument();
|
||||
expect(hostApiFetchMock.mock.calls.some(([path]) => ['/api/agent-browser/diagnostics', '/api/agent-browser/cdp/events'].includes(String(path)))).toBe(false);
|
||||
expect(hostApiFetchMock.mock.calls.some(([path]) => ['/api/agent-browser/close', '/api/agent-browser/open', '/api/agent-browser/navigate'].includes(String(path)))).toBe(false);
|
||||
|
||||
view.unmount();
|
||||
|
||||
Reference in new issue
Block a user