Merge guided cloud Agent UI
This commit is contained in:
147
tests/unit/cloud-agents-guidance.test.tsx
Normal file
147
tests/unit/cloud-agents-guidance.test.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import { useState } from 'react';
|
||||
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
|
||||
import { CloudApproval, CloudChat } from '@/pages/CloudAgents/CloudChat';
|
||||
import { AgentRequirements } from '@/pages/CloudAgents/AgentRequirements';
|
||||
import { CloudAccessPanel } from '@/pages/CloudAgents/CloudAccess';
|
||||
|
||||
const api = vi.hoisted(() => ({ call: vi.fn(), events: vi.fn(), download: vi.fn(), recovery: vi.fn(), remember: vi.fn() }));
|
||||
vi.mock('@/lib/cloud-agents-api', () => ({ cloudAgentsApi: api }));
|
||||
const slug = 'ml-' + 'a'.repeat(32);
|
||||
const trial = { thread_id: 'old-trial', client_thread_id: 'old-client', mode: 'preview', draft_revision: 1, status: 'completed' };
|
||||
const messages = [
|
||||
{ id: 'q1', role: 'user', content: '为什么会有彩虹?' },
|
||||
{ id: 'a1', role: 'assistant', content: '这是上次回答。[查看观察记录](sandbox:/mnt/data/note.txt)' },
|
||||
];
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
api.recovery.mockResolvedValue({ recent: null, pending: [] });
|
||||
api.remember.mockResolvedValue({ saved: true });
|
||||
api.download.mockResolvedValue(undefined);
|
||||
api.events.mockResolvedValue(Object.assign(new EventTarget(), { close: vi.fn() }));
|
||||
api.call.mockImplementation(async (operation: string) => {
|
||||
if (operation === 'threads') return { threads: [trial], next_offset: null };
|
||||
if (operation === 'history') return { thread_id: trial.thread_id, messages, run: null, next_offset: null };
|
||||
if (operation === 'attachments') return { attachments: [] };
|
||||
if (operation === 'files') return { files: [] };
|
||||
if (operation === 'viewed') return { viewed: true };
|
||||
throw new Error(operation);
|
||||
});
|
||||
});
|
||||
afterEach(cleanup);
|
||||
|
||||
it('starts private use through the existing publication action and retains its identity on retry', async () => {
|
||||
const base = api.call.getMockImplementation()!;
|
||||
let published = false;
|
||||
let attempts = 0;
|
||||
api.call.mockImplementation(async (operation, input) => {
|
||||
if (operation === 'access') return { published_version: published ? 1 : null, enabled: true, versions: [], grants: [], applications: [] };
|
||||
if (operation === 'publish') { if (++attempts === 1) throw new Error('连接中断'); published = true; return { version: 1 }; }
|
||||
if (operation === 'costs') return { items: [], next_offset: null, summary: {} };
|
||||
if (operation === 'channelBindings') return { items: [] };
|
||||
return base(operation, input);
|
||||
});
|
||||
const onPublished = vi.fn();
|
||||
const view = render(<CloudAccessPanel slug={slug} revision={2} onPublished={onPublished} />);
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '确认开始使用' })).toBeEnabled());
|
||||
expect(screen.queryByLabelText('应用名称')).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '确认开始使用' }));
|
||||
await screen.findByText(/连接中断/);
|
||||
view.rerender(<CloudAccessPanel slug={slug} revision={3} onPublished={onPublished} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: '重试本次发布' }));
|
||||
await screen.findByText('助手已准备好');
|
||||
const publications = api.call.mock.calls.filter(([operation]) => operation === 'publish');
|
||||
expect(publications).toHaveLength(2);
|
||||
expect(publications[0][1]).toEqual(publications[1][1]);
|
||||
expect(publications[1][1]).toMatchObject({ expected_revision: 2 });
|
||||
expect(onPublished).toHaveBeenCalledTimes(1);
|
||||
expect(api.call.mock.calls.some(([operation]) => operation === 'share' || operation === 'createApplication')).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps custom requirements intact while toggling and editing answer styles', () => {
|
||||
const onChange = vi.fn();
|
||||
function Requirements() {
|
||||
const [value, setValue] = useState('我的规则\n\n 保留缩进和解释。');
|
||||
return <AgentRequirements value={value} onChange={next => { onChange(next); setValue(next); }} />;
|
||||
}
|
||||
render(<Requirements />);
|
||||
fireEvent.click(screen.getByRole('button', { name: '简单易懂' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '先给我提示' }));
|
||||
expect(screen.getByLabelText('特别要求')).toHaveValue('我的规则\n\n 保留缩进和解释。');
|
||||
fireEvent.click(screen.getByRole('button', { name: '简单易懂' }));
|
||||
fireEvent.change(screen.getByLabelText('特别要求'), { target: { value: '我的新规则' } });
|
||||
expect(onChange).toHaveBeenLastCalledWith('我的新规则\n先给我提示和思考问题,让我尝试后再补充答案。');
|
||||
});
|
||||
|
||||
it('waits for a successful save, preserves the old trial and reuses its question without sending', async () => {
|
||||
let finish!: (revision: number) => void;
|
||||
const save = vi.fn(() => new Promise<number>(resolve => { finish = resolve; }));
|
||||
const view = render(<CloudChat slug={slug} previewRevision={1} hasUnsavedChanges onSaveForPreview={save} />);
|
||||
await screen.findByText('这是上次回答。');
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存并重新试用' }));
|
||||
expect(screen.getByLabelText('消息')).toBeDisabled();
|
||||
expect(screen.getByText('这是上次回答。')).toBeVisible();
|
||||
expect(screen.getByRole('button', { name: '新对话' })).toBeDisabled();
|
||||
await act(async () => { finish(2); });
|
||||
view.rerender(<CloudChat slug={slug} previewRevision={2} onSaveForPreview={save} />);
|
||||
expect(screen.getByText('当前试用 · 修订 2')).toBeVisible();
|
||||
expect(screen.getByLabelText('消息')).toHaveValue('为什么会有彩虹?');
|
||||
expect(screen.getByText('这是上次回答。')).not.toBeVisible();
|
||||
fireEvent.click(screen.getByText('对比上一次试用 · 修订 1'));
|
||||
expect(screen.getByText('这是上次回答。')).toBeVisible();
|
||||
fireEvent.click(screen.getByRole('button', { name: '查看观察记录' }));
|
||||
await waitFor(() => expect(api.download).toHaveBeenCalledWith('old-trial', '/mnt/data/note.txt'));
|
||||
expect(api.call.mock.calls.some(([operation]) => operation === 'preview' || operation === 'submit')).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps the old trial and typed question if saving fails and allows a deliberate retry', async () => {
|
||||
const save = vi.fn().mockResolvedValueOnce(undefined).mockResolvedValueOnce(2);
|
||||
render(<CloudChat slug={slug} previewRevision={1} hasUnsavedChanges onSaveForPreview={save} />);
|
||||
await screen.findByText('这是上次回答。');
|
||||
fireEvent.change(screen.getByLabelText('消息'), { target: { value: '我另外想问的问题' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存并重新试用' }));
|
||||
await waitFor(() => expect(screen.getByLabelText('消息')).toBeEnabled());
|
||||
expect(screen.getByText('当前试用 · 修订 1')).toBeVisible();
|
||||
expect(screen.getByText('这是上次回答。')).toBeVisible();
|
||||
expect(screen.getByLabelText('消息')).toHaveValue('我另外想问的问题');
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存并重新试用' }));
|
||||
await screen.findByText('当前试用 · 修订 2');
|
||||
expect(screen.getByLabelText('消息')).toHaveValue('我另外想问的问题');
|
||||
expect(save).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it.each(['running', 'queued', 'interrupted'])('does not start a replacement trial while the current trial is %s', async status => {
|
||||
const base = api.call.getMockImplementation()!;
|
||||
const run = { agent_run_id: 'run-1', thread_id: trial.thread_id, status,
|
||||
...(status === 'interrupted' ? { interrupt: { approval: { action_requests: [{ name: 'ls', args: { path: 'outputs' } }] } } } : {}),
|
||||
};
|
||||
api.call.mockImplementation(async (operation, input) => {
|
||||
if (operation === 'history') return { thread_id: trial.thread_id, messages, run: status === 'queued' ? null : run, next_offset: null,
|
||||
queued_requests: status === 'queued' ? [{ request_id: 'q2', thread_id: trial.thread_id, status: 'queued' }] : [],
|
||||
};
|
||||
if (operation === 'run') return run;
|
||||
if (operation === 'request') return { request_id: 'q2', thread_id: trial.thread_id, status: 'queued' };
|
||||
return base(operation, input);
|
||||
});
|
||||
const save = vi.fn();
|
||||
render(<CloudChat slug={slug} previewRevision={1} hasUnsavedChanges onSaveForPreview={save} />);
|
||||
await screen.findByText('这是上次回答。');
|
||||
expect(screen.getByRole('button', { name: '保存并重新试用' })).toBeDisabled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存并重新试用' }));
|
||||
expect(save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows actual action arguments and retries the same approval decision after a lost response', async () => {
|
||||
const submit = vi.fn().mockRejectedValueOnce(new Error('连接中断')).mockResolvedValueOnce(undefined);
|
||||
const actions = [{ name: 'execute', args: { command: 'create slides' } }, { name: 'ls', args: { path: 'outputs' } }];
|
||||
render(<CloudApproval runId="run-1" interrupt={{ approval: { action_requests: actions } }} submitDecision={submit} />);
|
||||
expect(screen.getByText('1. 运行命令')).toBeVisible();
|
||||
expect(screen.getByText('create slides')).toBeVisible();
|
||||
expect(screen.getByText('outputs')).toBeVisible();
|
||||
expect(screen.getAllByText('查看技术详情')).toHaveLength(2);
|
||||
fireEvent.click(screen.getByRole('button', { name: '允许执行' }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: '重试本次确认' }));
|
||||
await waitFor(() => expect(submit).toHaveBeenCalledTimes(2));
|
||||
expect(submit.mock.calls[0][0]).toEqual(submit.mock.calls[1][0]);
|
||||
expect(submit.mock.calls[1][0].decision).toEqual({ decisions: [{ type: 'approve' }, { type: 'approve' }] });
|
||||
});
|
||||
@@ -36,6 +36,103 @@ beforeEach(() => {
|
||||
});
|
||||
afterEach(cleanup);
|
||||
|
||||
it('does not label disabled or archived assistants as ready to use', async () => {
|
||||
api.list.mockResolvedValue({ agents: [
|
||||
{ ...draft, published_version: 1, enabled: false },
|
||||
{ ...draft, slug: 'ml-' + 'b'.repeat(32), name: '归档助手', published_version: 1, archived: true },
|
||||
], next_cursor: null });
|
||||
open();
|
||||
expect(await screen.findByText('已停用')).toBeVisible();
|
||||
expect(screen.getByText('已归档')).toBeVisible();
|
||||
expect(screen.queryByText('可以使用')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('protects a custom requirement before a name or purpose has been entered', async () => {
|
||||
const { router } = open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: '创建智能体', exact: true }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '我自己想一个' }));
|
||||
fireEvent.change(screen.getByLabelText('特别要求'), { target: { value: '请先听我说完' } });
|
||||
await act(async () => { await router.navigate('/module-select'); });
|
||||
expect(screen.getByRole('dialog', { name: '离开创建' })).toBeVisible();
|
||||
fireEvent.click(screen.getByRole('button', { name: '继续创建' }));
|
||||
expect(screen.getByLabelText('特别要求')).toHaveValue('请先听我说完');
|
||||
});
|
||||
|
||||
it('creates from an editable starter, preserves chosen requirements and defaults only a new assistant model', async () => {
|
||||
const base = api.call.getMockImplementation()!;
|
||||
api.call.mockImplementation(async (operation, input) => operation === 'catalog' ? {
|
||||
models: [{ id: 'model-first', name: '可用模型甲' }, { id: 'model-second', name: '可用模型乙' }], resources: {}, pricing: null,
|
||||
} : base(operation, input));
|
||||
api.create.mockImplementation(async input => ({ ...draft, ...input, system_prompt: input.purpose }));
|
||||
api.save.mockImplementation(async (_slug, input) => ({ ...draft, ...input, draft_revision: 2 }));
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: '创建智能体', exact: true }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /科学问答助手/ }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '先给我提示' }));
|
||||
fireEvent.change(screen.getByLabelText('特别要求'), { target: { value: '一次只解释一个新词。' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '下一步:试一试' }));
|
||||
await screen.findByTestId('cloud-agent-editor');
|
||||
await waitFor(() => expect(screen.getByLabelText('模型')).toHaveValue('model-first'));
|
||||
expect(screen.getByLabelText('特别要求')).toHaveValue('一次只解释一个新词。');
|
||||
expect(screen.getByRole('button', { name: '先给我提示' })).toHaveAttribute('aria-pressed', 'true');
|
||||
expect(screen.queryByRole('button', { name: '能力' })).not.toBeInTheDocument();
|
||||
fireEvent.change(screen.getByLabelText('模型'), { target: { value: '' } });
|
||||
fireEvent.change(screen.getByLabelText('名称'), { target: { value: '我的科学助手' } });
|
||||
expect(screen.getByLabelText('模型')).toHaveValue('');
|
||||
expect(screen.getByRole('button', { name: '保存并开始试用' })).toBeDisabled();
|
||||
fireEvent.change(screen.getByLabelText('模型'), { target: { value: 'model-second' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存并开始试用' }));
|
||||
await waitFor(() => expect(screen.getByLabelText('消息')).toBeEnabled());
|
||||
expect(api.save).toHaveBeenCalledWith(draft.slug, expect.objectContaining({
|
||||
name: '我的科学助手', system_prompt: '一次只解释一个新词。\n先给我提示和思考问题,让我尝试后再补充答案。',
|
||||
configuration: expect.objectContaining({ model: 'model-second' }),
|
||||
}));
|
||||
fireEvent.click(screen.getByRole('button', { name: '为什么雨后会出现彩虹?' }));
|
||||
expect(screen.getByLabelText('消息')).toHaveValue('为什么雨后会出现彩虹?');
|
||||
expect(api.call.mock.calls.some(([operation]) => operation === 'preview' || operation === 'submit')).toBe(false);
|
||||
});
|
||||
|
||||
it('saves renamed existing assistants without rewriting arbitrary instructions or selecting a model', async () => {
|
||||
const original = '自定义标题\n\n 保留缩进。\n简单易懂只是一个例子。\n';
|
||||
const existing = { ...draft, system_prompt: original };
|
||||
api.list.mockResolvedValue({ agents: [existing], next_cursor: null });
|
||||
const base = api.call.getMockImplementation()!;
|
||||
api.call.mockImplementation(async (operation, input) => operation === 'catalog' ? {
|
||||
models: [{ id: 'model-a', name: '模型甲' }], resources: {}, pricing: null,
|
||||
} : base(operation, input));
|
||||
api.save.mockImplementation(async (_slug, input) => ({ ...existing, ...input, draft_revision: 2 }));
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: /写作搭档/ }));
|
||||
await waitFor(() => expect(screen.getByLabelText('模型')).toBeEnabled());
|
||||
expect(screen.getByLabelText('模型')).toHaveValue('');
|
||||
expect(screen.getByLabelText('特别要求')).toHaveValue(original);
|
||||
fireEvent.change(screen.getByLabelText('名称'), { target: { value: '新名字' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
|
||||
await screen.findByRole('button', { name: '已保存' });
|
||||
expect(api.save).toHaveBeenCalledWith(draft.slug, expect.objectContaining({ system_prompt: original, configuration: expect.objectContaining({ model: '' }) }));
|
||||
});
|
||||
|
||||
it('retains requirements when model loading fails and offers a usable retry', async () => {
|
||||
const base = api.call.getMockImplementation()!;
|
||||
let fail = true;
|
||||
api.call.mockImplementation(async (operation, input) => {
|
||||
if (operation === 'catalog') {
|
||||
if (fail) throw new Error('目录暂不可用');
|
||||
return { models: [{ id: 'model-a', name: '模型甲' }], resources: {}, pricing: null };
|
||||
}
|
||||
return base(operation, input);
|
||||
});
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: /写作搭档/ }));
|
||||
await screen.findByText('目录暂不可用');
|
||||
fireEvent.change(screen.getByLabelText('特别要求'), { target: { value: '保留我的想法' } });
|
||||
fail = false;
|
||||
fireEvent.click(screen.getByRole('button', { name: '重新读取模型' }));
|
||||
await waitFor(() => expect(screen.getByLabelText('模型')).toBeEnabled());
|
||||
expect(screen.getByLabelText('特别要求')).toHaveValue('保留我的想法');
|
||||
expect(screen.getByLabelText('模型')).toHaveValue('');
|
||||
});
|
||||
|
||||
it('restores the exact previous thread and presents an uncertain operation without automatically replaying it', async () => {
|
||||
const published = { ...draft, published_version: 1 };
|
||||
const pending = { id: 'submit:pending', operation: 'submit', input: { slug: draft.slug, request_id: 'same-request', thread_id: 'older-thread', query: '上次未确认输入' }, created_at: '2026-09-10T01:00:00Z' };
|
||||
@@ -66,12 +163,13 @@ it('keeps creation input and its uncertain operation when an existing card is cl
|
||||
open();
|
||||
const existing = await screen.findByRole('button', { name: /写作搭档/ });
|
||||
fireEvent.click(screen.getByRole('button', { name: '创建智能体' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '我自己想一个' }));
|
||||
fireEvent.change(screen.getByLabelText('名称'), { target: { value: '新搭档' } });
|
||||
fireEvent.change(screen.getByLabelText('用途'), { target: { value: '新用途' } });
|
||||
fireEvent.click(existing);
|
||||
expect(screen.queryByTestId('cloud-agent-editor')).not.toBeInTheDocument();
|
||||
expect(screen.getByLabelText('名称')).toHaveValue('新搭档');
|
||||
fireEvent.click(screen.getByRole('button', { name: '创建草稿' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '下一步:试一试' }));
|
||||
await screen.findByRole('alert');
|
||||
fireEvent.click(existing);
|
||||
expect(screen.queryByTestId('cloud-agent-editor')).not.toBeInTheDocument();
|
||||
@@ -109,9 +207,10 @@ it('keeps the same creation operation after a lost response', async () => {
|
||||
api.create.mockRejectedValueOnce(new Error('连接中断')).mockResolvedValueOnce(draft);
|
||||
open();
|
||||
fireEvent.click(await screen.findByText('创建第一个智能体'));
|
||||
fireEvent.click(screen.getByRole('button', { name: '我自己想一个' }));
|
||||
fireEvent.change(screen.getByLabelText('名称'), { target: { value: '写作搭档' } });
|
||||
fireEvent.change(screen.getByLabelText('用途'), { target: { value: '帮助写作' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '创建草稿' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '下一步:试一试' }));
|
||||
await screen.findByRole('alert');
|
||||
fireEvent.click(screen.getByRole('button', { name: '重试创建' }));
|
||||
await screen.findByTestId('cloud-agent-editor');
|
||||
@@ -124,10 +223,10 @@ it('preserves local input on conflicts and requires an explicit choice before re
|
||||
api.get.mockResolvedValue({ ...draft, draft_revision: 2, system_prompt: '其他设备内容' });
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: /写作搭档/ }));
|
||||
fireEvent.change(screen.getByLabelText('角色与指令'), { target: { value: '我的修改' } });
|
||||
fireEvent.change(screen.getByLabelText('特别要求'), { target: { value: '我的修改' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
|
||||
await screen.findByText('云端已有修订 2,本地输入仍保留');
|
||||
expect(screen.getByLabelText('角色与指令')).toHaveValue('我的修改');
|
||||
expect(screen.getByLabelText('特别要求')).toHaveValue('我的修改');
|
||||
expect(screen.getByRole('button', { name: '保存草稿' })).toBeDisabled();
|
||||
fireEvent.click(screen.getByText('保留我的内容,继续编辑'));
|
||||
api.save.mockResolvedValue({ ...draft, draft_revision: 3, system_prompt: '我的修改' });
|
||||
@@ -159,7 +258,7 @@ it('clears the previous account workspace and ignores its delayed list', async (
|
||||
const view = open();
|
||||
auth.user.userId = 'other';
|
||||
view.rerender(<RouterProvider key="other-account" router={view.router} />);
|
||||
await screen.findByText('你的第一个智能体,从这里开始');
|
||||
await screen.findByText('做一个懂你的小助手');
|
||||
await act(async () => finish({ agents: [draft], next_cursor: null }));
|
||||
await waitFor(() => expect(screen.queryByText('写作搭档')).not.toBeInTheDocument());
|
||||
});
|
||||
@@ -169,9 +268,10 @@ it('warns before leaving an uncertain creation and keeps its retry intent', asyn
|
||||
api.create.mockRejectedValue(new Error('连接中断'));
|
||||
const { router } = open();
|
||||
fireEvent.click(await screen.findByText('创建第一个智能体'));
|
||||
fireEvent.click(screen.getByRole('button', { name: '我自己想一个' }));
|
||||
fireEvent.change(screen.getByLabelText('名称'), { target: { value: '写作搭档' } });
|
||||
fireEvent.change(screen.getByLabelText('用途'), { target: { value: '帮助写作' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '创建草稿' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '下一步:试一试' }));
|
||||
await screen.findByRole('alert');
|
||||
await act(async () => { await router.navigate('/module-select'); });
|
||||
expect(screen.getByRole('dialog', { name: '离开创建' })).toBeVisible();
|
||||
@@ -192,17 +292,17 @@ it('keeps unsent preview text when configuration changes and waits before switch
|
||||
fireEvent.change(prompt, { target: { value: '还未发送的问题' } });
|
||||
fireEvent.change(screen.getByLabelText('名称'), { target: { value: '新的名称' } });
|
||||
expect(prompt).toHaveValue('还未发送的问题');
|
||||
expect(screen.getByText(/保存不会清空当前试用/)).toBeVisible();
|
||||
expect(screen.getByText(/要求改好了/)).toBeVisible();
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '保存草稿' })).toBeEnabled());
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
|
||||
await screen.findByRole('button', { name: '已保存' });
|
||||
expect(api.save).toHaveBeenCalledTimes(1);
|
||||
expect(screen.getByLabelText('消息')).toBe(prompt);
|
||||
expect(prompt).toHaveValue('还未发送的问题');
|
||||
expect(screen.getByText('草稿预览 · 修订 1')).toBeVisible();
|
||||
expect(screen.getByText('当前试用 · 修订 1')).toBeVisible();
|
||||
expect(screen.getByRole('button', { name: '发送', exact: true })).toBeDisabled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '用最新草稿试用' }));
|
||||
expect(screen.getByText('草稿预览 · 修订 2')).toBeVisible();
|
||||
await waitFor(() => expect(screen.getByText('当前试用 · 修订 2')).toBeVisible());
|
||||
expect(prompt).toHaveValue('还未发送的问题');
|
||||
expect(screen.getByRole('button', { name: '发送', exact: true })).toBeEnabled();
|
||||
});
|
||||
@@ -217,18 +317,19 @@ it('keeps preview text across mode switches and saves a budget independently of
|
||||
fireEvent.click(await screen.findByRole('button', { name: /写作搭档/ }));
|
||||
await waitFor(() => expect(screen.getByLabelText('消息')).toBeEnabled());
|
||||
fireEvent.change(screen.getByLabelText('消息'), { target: { value: '待发送的问题' } });
|
||||
fireEvent.change(screen.getByLabelText('角色与指令'), { target: { value: '未保存的指令' } });
|
||||
fireEvent.change(screen.getByLabelText('特别要求'), { target: { value: '未保存的指令' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '对话', exact: true }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '编辑', exact: true }));
|
||||
expect(screen.getByLabelText('消息')).toHaveValue('待发送的问题');
|
||||
expect(screen.getByLabelText('角色与指令')).toHaveValue('未保存的指令');
|
||||
expect(screen.getByLabelText('特别要求')).toHaveValue('未保存的指令');
|
||||
fireEvent.click(screen.getByRole('button', { name: '高级设置' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '限制', exact: true }));
|
||||
fireEvent.change(screen.getByLabelText('每日上限(词元点数)'), { target: { value: '12' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存费用上限' }));
|
||||
await waitFor(() => expect(api.call).toHaveBeenCalledWith('saveBudget', expect.objectContaining({ daily_limit_points: '12' })));
|
||||
expect(api.save).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '指令', exact: true }));
|
||||
expect(screen.getByLabelText('角色与指令')).toHaveValue('未保存的指令');
|
||||
fireEvent.click(screen.getByRole('button', { name: '我的要求', exact: true }));
|
||||
expect(screen.getByLabelText('特别要求')).toHaveValue('未保存的指令');
|
||||
expect(screen.getByLabelText('消息')).toHaveValue('待发送的问题');
|
||||
});
|
||||
|
||||
@@ -242,6 +343,7 @@ it('keeps an existing preview when the saved draft clears its model selection',
|
||||
await waitFor(() => expect(screen.getByLabelText('消息')).toBeEnabled());
|
||||
const prompt = screen.getByLabelText('消息');
|
||||
fireEvent.change(prompt, { target: { value: '还没有发送' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '高级设置' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '能力', exact: true }));
|
||||
fireEvent.change(screen.getByLabelText('模型'), { target: { value: '' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
|
||||
@@ -262,6 +364,7 @@ it('searches capabilities while retaining existing selections outside the result
|
||||
api.save.mockImplementation(async (_slug, input) => ({ ...configured, ...input, draft_revision: 2 }));
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: /写作搭档/ }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '高级设置' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '能力', exact: true }));
|
||||
expect(await screen.findByRole('checkbox', { name: /missing-tool/ })).toBeChecked();
|
||||
fireEvent.click(screen.getByRole('button', { name: '选择工具', exact: true }));
|
||||
|
||||
@@ -187,7 +187,7 @@ it('keeps publication and access controls usable when cost history is unavailabl
|
||||
throw new Error(operation);
|
||||
});
|
||||
render(<CloudAccessPanel slug={slug} revision={2} onPublished={() => undefined} />);
|
||||
expect(await screen.findByText('当前发布版本 1')).toBeVisible();
|
||||
expect(await screen.findByText('助手已准备好')).toBeVisible();
|
||||
expect(screen.getByRole('button', { name: '停用智能体' })).toBeEnabled();
|
||||
expect(await screen.findByText('费用暂不可用')).toBeVisible();
|
||||
});
|
||||
@@ -382,11 +382,13 @@ it('pins uncertain preview retries to the original request and revision after a
|
||||
expect(submissions[0][1]).toEqual(submissions[1][1]);
|
||||
expect(submissions[1][1]).toMatchObject({ expected_revision: 1, query: '原来的问题' });
|
||||
fireEvent.click(screen.getByRole('button', { name: '用最新草稿试用' }));
|
||||
expect(screen.queryByText('第一版回答')).not.toBeInTheDocument();
|
||||
await waitFor(() => expect(screen.getByLabelText('消息')).toBeEnabled());
|
||||
expect(screen.getByText('对比上一次试用 · 修订 1')).toBeVisible();
|
||||
expect(screen.getByText('第一版回答')).not.toBeVisible();
|
||||
fireEvent.click(screen.getByRole('button', { name: '查看历史对话' }));
|
||||
fireEvent.change(screen.getByLabelText('历史对话'), { target: { value: 'thread-1' } });
|
||||
await screen.findByText('第一版回答');
|
||||
expect(screen.getByText('草稿预览 · 修订 1')).toBeVisible();
|
||||
expect(screen.getByText('当前试用 · 修订 1')).toBeVisible();
|
||||
});
|
||||
|
||||
it('uses Enter to send while preserving Shift+Enter and Chinese IME composition', async () => {
|
||||
|
||||
@@ -53,7 +53,7 @@ it.each([
|
||||
api.call.mockImplementation(async (operation, input) => operation === 'channelConversation'
|
||||
? { ...history, run: { ...run, status } } : base(operation, input));
|
||||
render(<CloudChannelConversations slug={session.agent_slug} />);
|
||||
expect(await screen.findByTestId('wechat-run-status')).toHaveTextContent('任务状态:' + label);
|
||||
await waitFor(() => expect(screen.getByTestId('wechat-run-status')).toHaveTextContent('任务状态:' + label));
|
||||
});
|
||||
|
||||
it('keeps partial assistant output visible while surfacing a closed run failure and its authoritative error', async () => {
|
||||
|
||||
Reference in New Issue
Block a user