fix(agents): explain missing channel targets

This commit is contained in:
鲨鱼辣椒 committed 2026-10-13 11:32:07 +08:00
1 parent 13336cfe12
commit 8289f27f3e
5 files changed
+222 -9

No files matched your search

+84 -5
View File
@@ -1,5 +1,5 @@
import { act, cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { MemoryRouter, Route, Routes, useLocation } from 'react-router-dom';
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
import { ChannelAccounts } from '@/pages/CloudAgents/ChannelAccounts';
import { EMPTY_CLOUD_CONFIGURATION, type CloudAgentDraft, type CloudUserChannelAccount, type CloudWechatBindState } from '../../shared/cloud-agents';
@@ -23,7 +23,16 @@ let verifyStatus: CloudWechatBindState['status'];
const calls = (operation: string) => api.call.mock.calls.filter(([name]) => name === operation);
const writes = () => api.call.mock.calls.filter(([name]) => !['channelAccounts', 'channelAccountWechatBindStatus'].includes(name));
function renderPage(entry = '/cloud-agents/channels') { return render(<MemoryRouter initialEntries={[entry]}><ChannelAccounts /></MemoryRouter>); }
function LocationProbe() {
const location = useLocation();
return <output data-testid="location-path">{location.pathname + location.search}</output>;
}
function renderPage(entry = '/cloud-agents/channels') {
return render(<MemoryRouter initialEntries={[entry]}><Routes>
<Route path="/cloud-agents/channels" element={<ChannelAccounts />} />
<Route path="/cloud-agents" element={<div>智能体列表</div>} />
</Routes><LocationProbe /></MemoryRouter>);
}
async function addWechat(fakeTimers = false) {
fireEvent.click(await screen.findByRole('button', { name: '添加微信', exact: true }));
const dialog = screen.getByRole('dialog', { name: '添加微信', exact: true });
@@ -80,6 +89,69 @@ beforeEach(() => {
});
afterEach(() => { cleanup(); vi.useRealTimers(); });
it.each([
['no Agents', []],
['only drafts', [{ ...agentA, published_version: null }]],
['only disabled Agents', [{ ...agentA, enabled: false }]],
] satisfies [string, CloudAgentDraft[]][])('guides a workspace with %s to My Agents without starting a binding', async (_name, unavailable) => {
agents = unavailable; accounts = [];
renderPage();
expect(await screen.findByRole('heading', { name: '还没有可连接的智能体', exact: true })).toBeVisible();
expect(screen.getByText('先创建一个智能体,配置完成后点击“开始使用”,就能连接到微信。', { exact: true })).toBeVisible();
expect(screen.queryByRole('button', { name: '添加第一个微信', exact: true })).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: '添加微信', exact: true })).toBeEnabled();
fireEvent.click(screen.getByRole('button', { name: '去我的智能体', exact: true }));
expect(await screen.findByText('智能体列表', { exact: true })).toBeVisible();
expect(screen.getByTestId('location-path')).toHaveTextContent('/cloud-agents?view=mine');
expect(writes()).toEqual([]);
});
it('uses the same preparation hint in the add dialog without duplicating it on the page', async () => {
agents = []; accounts = [];
renderPage();
await screen.findByRole('heading', { name: '还没有可连接的智能体', exact: true });
fireEvent.click(screen.getByRole('button', { name: '添加微信', exact: true }));
const dialog = screen.getByRole('dialog', { name: '添加微信', exact: true });
expect(within(dialog).getByRole('heading', { name: '还没有可连接的智能体', exact: true })).toBeVisible();
expect(within(dialog).getByText('先创建一个智能体,配置完成后点击“开始使用”,就能连接到微信。', { exact: true })).toBeVisible();
expect(screen.getAllByRole('heading', { name: '还没有可连接的智能体', exact: true, hidden: true })).toHaveLength(1);
expect(within(dialog).getByRole('button', { name: '下一步:扫码连接', exact: true })).toBeDisabled();
fireEvent.click(within(dialog).getByRole('button', { name: '去我的智能体', exact: true }));
expect(await screen.findByText('智能体列表', { exact: true })).toBeVisible();
expect(screen.getByTestId('location-path')).toHaveTextContent('/cloud-agents?view=mine');
expect(writes()).toEqual([]);
});
it('waits for the Agent read before showing a preparation hint', async () => {
accounts = [];
let finish!: (value: { agents: CloudAgentDraft[]; next_cursor: null }) => void;
api.list.mockImplementation(() => new Promise(resolve => { finish = resolve; }));
renderPage();
expect(screen.getByText('正在查看你的微信绑定…', { exact: true })).toBeVisible();
expect(screen.queryByRole('heading', { name: '还没有可连接的智能体', exact: true })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: '去我的智能体', exact: true })).not.toBeInTheDocument();
await act(async () => finish({ agents: [], next_cursor: null }));
expect(await screen.findByRole('heading', { name: '还没有可连接的智能体', exact: true })).toBeVisible();
expect(writes()).toEqual([]);
});
it('keeps existing WeChat cards manageable when no connectable Agent remains', async () => {
agents = [];
renderPage();
expect(await screen.findByRole('heading', { name: '还没有可连接的智能体', exact: true })).toBeVisible();
expect(screen.getByRole('article', { name: '原有微信微信', exact: true })).toBeVisible();
expect(screen.getByRole('article', { name: '备用微信微信', exact: true })).toBeVisible();
const dialog = await manage();
expect(within(dialog).getByRole('button', { name: '暂停使用', exact: true })).toBeEnabled();
expect(within(dialog).getByRole('button', { name: '解除绑定', exact: true })).toBeEnabled();
expect(within(dialog).getByRole('button', { name: '断开微信连接', exact: true })).toBeEnabled();
fireEvent.click(within(dialog).getByRole('button', { name: '暂停使用', exact: true }));
await waitFor(() => expect(calls('pauseChannelAccount')).toHaveLength(1));
expect(accounts[0]).toMatchObject({ enabled: false, health: 'connected', target_agent_slug: agentA.slug });
expect(calls('createChannelAccount')).toHaveLength(0);
expect(calls('routeChannelAccount')).toHaveLength(0);
});
it('shows binding cards and never fetches or embeds conversation or activity panels', async () => {
renderPage();
const card = await screen.findByRole('article', { name: '原有微信微信', exact: true });
@@ -89,6 +161,7 @@ it('shows binding cards and never fetches or embeds conversation or activity pan
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
expect(screen.queryByText('微信对话', { exact: true })).not.toBeInTheDocument();
expect(screen.queryByText('活动记录', { exact: true })).not.toBeInTheDocument();
expect(screen.queryByRole('heading', { name: '还没有可连接的智能体', exact: true })).not.toBeInTheDocument();
expect(writes()).toEqual([]);
expect(api.call.mock.calls.every(([operation]) => operation === 'channelAccounts')).toBe(true);
});
@@ -295,13 +368,19 @@ it('does not call a blocked or unconfirmed existing account ready to use', async
});
it('keeps adding unavailable after a failed initial read instead of showing a false empty account list', async () => {
api.call.mockRejectedValue(new Error('微信列表暂时不可用'));
it.each(['accounts', 'agents'])('keeps adding unavailable after a failed initial %s read instead of showing a false empty list', async source => {
agents = [];
const message = source === 'accounts' ? '微信列表暂时不可用' : '智能体列表暂时不可用';
if (source === 'accounts') api.call.mockRejectedValue(new Error(message));
else api.list.mockRejectedValue(new Error(message));
renderPage();
await screen.findByText('微信列表暂时不可用', { exact: true });
await screen.findByText(message, { exact: true });
expect(screen.getByRole('button', { name: '添加微信', exact: true })).toBeDisabled();
expect(screen.queryByText('还没有微信绑定', { exact: true })).not.toBeInTheDocument();
expect(screen.queryByRole('heading', { name: '还没有可连接的智能体', exact: true })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: '去我的智能体', exact: true })).not.toBeInTheDocument();
installMock();
api.list.mockImplementation(async () => ({ agents, next_cursor: null }));
fireEvent.click(screen.getByRole('button', { name: '刷新微信列表', exact: true }));
expect(await screen.findByRole('article', { name: '原有微信微信', exact: true })).toBeVisible();
expect(screen.getByRole('button', { name: '添加微信', exact: true })).toBeEnabled();