Simplify Agent settings and start conversations directly
This commit is contained in:
1 parent
f7b1003c4c
commit
0a43ae901f
9 files changed
+402
-102
No files matched your search
@@ -0,0 +1,236 @@
|
||||
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { createMemoryRouter, RouterProvider } from 'react-router-dom';
|
||||
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
|
||||
import CloudAgents from '@/pages/CloudAgents';
|
||||
import { EMPTY_CLOUD_CONFIGURATION } from '../../shared/cloud-agents';
|
||||
|
||||
const api = vi.hoisted(() => ({ list: vi.fn(), create: vi.fn(), get: vi.fn(), save: vi.fn(), call: vi.fn(), events: vi.fn(), recovery: vi.fn(), remember: vi.fn() }));
|
||||
const auth = vi.hoisted(() => ({ user: { userId: 'creator' } }));
|
||||
vi.mock('@/lib/cloud-agents-api', () => ({ cloudAgentsApi: api }));
|
||||
vi.mock('@/stores/auth', () => ({ useAuthStore: (selector: (state: typeof auth) => unknown) => selector(auth) }));
|
||||
|
||||
const draft = {
|
||||
slug: 'ml-' + 'a'.repeat(32), name: '写作搭档', purpose: '', system_prompt: '原始指令',
|
||||
draft_revision: 3, updated_at: '2026-09-10T00:00:00Z',
|
||||
configuration: { ...EMPTY_CLOUD_CONFIGURATION, model: 'model-a' }, published_version: null, enabled: true,
|
||||
};
|
||||
|
||||
function open() {
|
||||
const router = createMemoryRouter([
|
||||
{ path: '/cloud-agents', element: <CloudAgents /> },
|
||||
{ path: '/module-select', element: <div>模块首页</div> },
|
||||
], { initialEntries: ['/cloud-agents'] });
|
||||
return { router, ...render(<RouterProvider router={router} />) };
|
||||
}
|
||||
async function openConfiguration() {
|
||||
const view = open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '开始使用', exact: true })).toBeEnabled());
|
||||
return view;
|
||||
}
|
||||
const publications = () => api.call.mock.calls.filter(([operation]) => operation === 'publish');
|
||||
const messageOperations = () => api.call.mock.calls.filter(([operation]) => ['preview', 'submit', 'resume'].includes(operation));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks(); auth.user.userId = 'creator';
|
||||
api.list.mockResolvedValue({ agents: [draft], next_cursor: null });
|
||||
api.get.mockResolvedValue(draft);
|
||||
api.recovery.mockResolvedValue({ recent: null, pending: [] });
|
||||
api.remember.mockResolvedValue({ saved: true });
|
||||
api.save.mockImplementation(async (_slug, input) => ({ ...draft, ...input, draft_revision: 4 }));
|
||||
api.call.mockImplementation(async (operation, input) => {
|
||||
if (operation === 'access') return { published_version: null, enabled: true, versions: [], grants: [], applications: [] };
|
||||
if (operation === 'catalog') return { models: [{ id: 'model-a', name: '模型甲' }], resources: {}, pricing: null };
|
||||
if (operation === 'knowledge') return { databases: [], models: [] };
|
||||
if (operation === 'resources') return { mcps: [], skills: [], subagents: [] };
|
||||
if (operation === 'threads') return { threads: [], next_offset: null };
|
||||
if (operation === 'publish') return { version: 1, draft_revision: input.expected_revision };
|
||||
return {};
|
||||
});
|
||||
});
|
||||
afterEach(cleanup);
|
||||
|
||||
it('starts once from the saved configuration and opens conversation only after activation succeeds', async () => {
|
||||
let finish!: (result: { version: number; draft_revision: number }) => void;
|
||||
const base = api.call.getMockImplementation()!;
|
||||
api.call.mockImplementation((operation, input) => operation === 'publish'
|
||||
? new Promise(resolve => { finish = resolve; }) : base(operation, input));
|
||||
const { router } = await openConfiguration();
|
||||
expect(publications()).toHaveLength(0);
|
||||
const start = screen.getByRole('button', { name: '开始使用', exact: true });
|
||||
fireEvent.click(start); fireEvent.click(start);
|
||||
expect(publications()).toHaveLength(1);
|
||||
expect(publications()[0][1]).toMatchObject({ slug: draft.slug, expected_revision: 3, operation_id: expect.any(String) });
|
||||
expect(screen.getByRole('button', { name: '正在开始…' })).toBeDisabled();
|
||||
expect(screen.queryByRole('region', { name: '智能体对话' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('准备好使用自己的助手了吗?')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '确认开始使用' })).not.toBeInTheDocument();
|
||||
expect(screen.getByLabelText('提示词')).toHaveValue('原始指令');
|
||||
await act(async () => { await router.navigate('/module-select'); });
|
||||
expect(screen.getByRole('dialog', { name: '未保存的修改' })).toBeVisible();
|
||||
fireEvent.click(screen.getByRole('button', { name: '继续编辑' }));
|
||||
await act(async () => finish({ version: 1, draft_revision: 3 }));
|
||||
expect(await screen.findByRole('region', { name: '智能体对话' })).toBeVisible();
|
||||
expect(screen.getByRole('button', { name: '调整智能体', exact: true })).toBeVisible();
|
||||
expect(api.save).not.toHaveBeenCalled();
|
||||
expect(messageOperations()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('keeps unsaved input in configuration until the user saves, then starts the saved revision directly', async () => {
|
||||
await openConfiguration();
|
||||
fireEvent.change(screen.getByLabelText('提示词'), { target: { value: '我的新要求' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '开始使用', exact: true }));
|
||||
expect(screen.getByRole('alert')).toHaveTextContent('请先保存草稿,再开始使用。');
|
||||
expect(screen.getByLabelText('提示词')).toBeVisible();
|
||||
expect(screen.getByLabelText('提示词')).toHaveValue('我的新要求');
|
||||
expect(publications()).toHaveLength(0);
|
||||
expect(api.save).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存草稿', exact: true }));
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '已保存', exact: true })).toBeDisabled());
|
||||
fireEvent.click(screen.getByRole('button', { name: '开始使用', exact: true }));
|
||||
expect(await screen.findByRole('region', { name: '智能体对话' })).toBeVisible();
|
||||
expect(publications()[0][1]).toMatchObject({ expected_revision: 4 });
|
||||
expect(messageOperations()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('returns to the model choice without activating a draft that has no model', async () => {
|
||||
api.list.mockResolvedValue({ agents: [{ ...draft, configuration: EMPTY_CLOUD_CONFIGURATION }], next_cursor: null });
|
||||
api.get.mockResolvedValue({ ...draft, configuration: EMPTY_CLOUD_CONFIGURATION });
|
||||
await openConfiguration();
|
||||
fireEvent.click(screen.getByRole('button', { name: '开始使用', exact: true }));
|
||||
expect(screen.getByRole('tab', { name: '高级设置' })).toHaveAttribute('aria-selected', 'true');
|
||||
expect(screen.getByLabelText('模型')).toBeVisible();
|
||||
expect(screen.getByRole('alert')).toHaveTextContent('请先在高级设置中选择模型。');
|
||||
expect(publications()).toHaveLength(0);
|
||||
expect(screen.queryByRole('region', { name: '智能体对话' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not activate while a cloud save conflict needs to be resolved', async () => {
|
||||
await openConfiguration();
|
||||
api.save.mockRejectedValue(new Error('保存冲突'));
|
||||
api.get.mockResolvedValue({ ...draft, draft_revision: 4, system_prompt: '云端修改' });
|
||||
fireEvent.change(screen.getByLabelText('提示词'), { target: { value: '保留本地修改' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存草稿', exact: true }));
|
||||
await screen.findByText('云端已有修订 4,本地输入仍保留');
|
||||
const start = screen.getByRole('button', { name: '开始使用', exact: true });
|
||||
expect(start).toBeDisabled();
|
||||
fireEvent.click(start);
|
||||
expect(publications()).toHaveLength(0);
|
||||
expect(screen.getByLabelText('提示词')).toHaveValue('保留本地修改');
|
||||
});
|
||||
|
||||
it('waits for a readable activation status and permits a read retry before starting', async () => {
|
||||
const base = api.call.getMockImplementation()!;
|
||||
let unavailable = true;
|
||||
api.call.mockImplementation(async (operation, input) => {
|
||||
if (operation === 'access' && unavailable) throw new Error('状态读取失败');
|
||||
return base(operation, input);
|
||||
});
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
|
||||
await screen.findByText(/状态读取失败/);
|
||||
expect(screen.getByRole('button', { name: '开始使用', exact: true })).toBeDisabled();
|
||||
expect(publications()).toHaveLength(0);
|
||||
unavailable = false;
|
||||
fireEvent.click(screen.getByRole('button', { name: '刷新应用状态', exact: true }));
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '开始使用', exact: true })).toBeEnabled());
|
||||
expect(publications()).toHaveLength(0);
|
||||
fireEvent.click(screen.getByRole('button', { name: '开始使用', exact: true }));
|
||||
expect(await screen.findByRole('region', { name: '智能体对话' })).toBeVisible();
|
||||
expect(publications()).toHaveLength(1);
|
||||
expect(messageOperations()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('retries an uncertain activation with the original operation and keeps pending-leave protection', async () => {
|
||||
const base = api.call.getMockImplementation()!;
|
||||
let attempts = 0;
|
||||
api.call.mockImplementation(async (operation, input) => {
|
||||
if (operation === 'publish' && ++attempts === 1) throw new Error('连接中断');
|
||||
return base(operation, input);
|
||||
});
|
||||
await openConfiguration();
|
||||
fireEvent.click(screen.getByRole('button', { name: '开始使用', exact: true }));
|
||||
await screen.findByText(/连接中断/);
|
||||
expect(screen.queryByRole('region', { name: '智能体对话' })).not.toBeInTheDocument();
|
||||
const unload = new Event('beforeunload', { cancelable: true });
|
||||
window.dispatchEvent(unload);
|
||||
expect(unload.defaultPrevented).toBe(true);
|
||||
fireEvent.click(screen.getByRole('button', { name: '重试开始使用', exact: true }));
|
||||
expect(await screen.findByRole('region', { name: '智能体对话' })).toBeVisible();
|
||||
expect(publications()).toHaveLength(2);
|
||||
expect(publications()[1][1]).toEqual(publications()[0][1]);
|
||||
expect(messageOperations()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('keeps a newer saved revision unapplied when retry resolves the original uncertain activation', async () => {
|
||||
const base = api.call.getMockImplementation()!;
|
||||
let attempts = 0;
|
||||
let activated = false;
|
||||
api.call.mockImplementation(async (operation, input) => {
|
||||
if (operation === 'access') return {
|
||||
published_version: activated ? 1 : null, enabled: true, grants: [], applications: [],
|
||||
versions: activated ? [{ version: 1, draft_revision: 3, created_at: '' }] : [],
|
||||
};
|
||||
if (operation === 'publish') {
|
||||
if (++attempts === 1) throw new Error('连接中断');
|
||||
activated = true;
|
||||
return { version: 1, draft_revision: input.expected_revision };
|
||||
}
|
||||
return base(operation, input);
|
||||
});
|
||||
await openConfiguration();
|
||||
fireEvent.click(screen.getByRole('button', { name: '开始使用', exact: true }));
|
||||
await screen.findByText(/连接中断/);
|
||||
fireEvent.change(screen.getByLabelText('提示词'), { target: { value: '这次新保存的要求' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存草稿', exact: true }));
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '已保存', exact: true })).toBeDisabled());
|
||||
fireEvent.click(screen.getByRole('button', { name: '重试开始使用', exact: true }));
|
||||
expect(await screen.findByRole('region', { name: '智能体对话' })).toBeVisible();
|
||||
await screen.findByText('修改已保存,尚未应用');
|
||||
expect(screen.getByText('这次修改还没有应用到正在使用的助手')).toBeVisible();
|
||||
expect(screen.getByRole('button', { name: '应用最新修改', exact: true })).toBeEnabled();
|
||||
expect(screen.getByLabelText('提示词')).toHaveValue('这次新保存的要求');
|
||||
expect(api.save).toHaveBeenCalledWith(draft.slug, expect.objectContaining({ expected_revision: 3, system_prompt: '这次新保存的要求' }));
|
||||
expect(publications()).toHaveLength(2);
|
||||
expect(publications()[1][1]).toEqual(publications()[0][1]);
|
||||
expect(publications()[1][1]).toMatchObject({ expected_revision: 3 });
|
||||
expect(messageOperations()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('keeps successful activation and conversation when the following status read fails', async () => {
|
||||
const base = api.call.getMockImplementation()!;
|
||||
let activated = false;
|
||||
api.call.mockImplementation(async (operation, input) => {
|
||||
if (operation === 'access' && activated) throw new Error('状态刷新失败');
|
||||
const result = await base(operation, input);
|
||||
if (operation === 'publish') activated = true;
|
||||
return result;
|
||||
});
|
||||
await openConfiguration();
|
||||
fireEvent.click(screen.getByRole('button', { name: '开始使用', exact: true }));
|
||||
expect(await screen.findByRole('region', { name: '智能体对话' })).toBeVisible();
|
||||
await screen.findByText(/状态刷新失败/);
|
||||
expect(screen.getByRole('button', { name: '已应用', exact: true })).toBeDisabled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '刷新应用状态', exact: true }));
|
||||
await waitFor(() => expect(api.call.mock.calls.filter(([operation]) => operation === 'access')).toHaveLength(3));
|
||||
expect(publications()).toHaveLength(1);
|
||||
expect(messageOperations()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('does not carry a late activation result into another signed-in account', async () => {
|
||||
let finish!: (result: { version: number; draft_revision: number }) => void;
|
||||
const base = api.call.getMockImplementation()!;
|
||||
api.call.mockImplementation((operation, input) => operation === 'publish'
|
||||
? new Promise(resolve => { finish = resolve; }) : base(operation, input));
|
||||
const view = await openConfiguration();
|
||||
fireEvent.click(screen.getByRole('button', { name: '开始使用', exact: true }));
|
||||
auth.user.userId = 'other';
|
||||
api.list.mockResolvedValue({ agents: [], next_cursor: null });
|
||||
view.rerender(<RouterProvider key="other-account" router={view.router} />);
|
||||
await screen.findByText('做一个属于你的 AI 助手吧');
|
||||
await act(async () => finish({ version: 1, draft_revision: 3 }));
|
||||
expect(screen.queryByTestId('cloud-agent-editor')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('region', { name: '智能体对话' })).not.toBeInTheDocument();
|
||||
expect(publications()).toHaveLength(1);
|
||||
expect(messageOperations()).toHaveLength(0);
|
||||
});
|
||||
@@ -425,6 +425,7 @@ it('requires an explicit advanced model choice before starting use but permits a
|
||||
api.call.mockImplementation(async (operation, input) => {
|
||||
if (operation === 'catalog') return { models: [{ id: 'model-a', name: '模型甲' }], resources: {}, pricing: null };
|
||||
if (operation === 'access') return { published_version: null, enabled: true, versions: [], grants: [], applications: [] };
|
||||
if (operation === 'publish') return { version: 1, draft_revision: input.expected_revision };
|
||||
return base(operation, input);
|
||||
});
|
||||
api.save.mockImplementation(async (_slug, input) => (current = { ...current, ...input, draft_revision: current.draft_revision + 1 }));
|
||||
@@ -450,11 +451,13 @@ it('requires an explicit advanced model choice before starting use but permits a
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
|
||||
await screen.findByRole('button', { name: '已保存' });
|
||||
fireEvent.click(screen.getByRole('button', { name: '开始使用', exact: true }));
|
||||
expect(await screen.findByRole('button', { name: '确认开始使用' })).toBeEnabled();
|
||||
expect(api.call.mock.calls.some(([operation]) => ['publish', 'preview', 'submit'].includes(operation))).toBe(false);
|
||||
expect(await screen.findByRole('region', { name: '智能体对话' })).toBeVisible();
|
||||
expect(screen.queryByRole('button', { name: '确认开始使用' })).not.toBeInTheDocument();
|
||||
expect(api.call.mock.calls.filter(([operation]) => operation === 'publish')).toHaveLength(1);
|
||||
expect(api.call.mock.calls.some(([operation]) => ['preview', 'submit'].includes(operation))).toBe(false);
|
||||
});
|
||||
|
||||
it('shows all advanced settings together and preserves unfinished selections, search and limits when switching pages', async () => {
|
||||
it('shows only model and capability settings together and preserves unfinished selections and search when switching pages', async () => {
|
||||
const base = api.call.getMockImplementation()!;
|
||||
api.call.mockImplementation(async (operation, input) => {
|
||||
if (operation === 'catalog') return {
|
||||
@@ -479,11 +482,13 @@ it('shows all advanced settings together and preserves unfinished selections, se
|
||||
expect(toolbox).toBeVisible();
|
||||
expect(handbook).toBeVisible();
|
||||
expect(model.compareDocumentPosition(toolbox) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
expect(screen.getByText('选它做事时能用的工具。', { exact: true })).toBeVisible();
|
||||
expect(screen.getByText('给它一本手册,照着方法做。', { exact: true })).toBeVisible();
|
||||
expect(screen.queryByText('选它做事时能用的工具。', { exact: true })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('给它一本手册,照着方法做。', { exact: true })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('group', { name: /^知识库/ })).toBeVisible();
|
||||
expect(screen.getByRole('heading', { name: '费用上限', exact: true })).toBeVisible();
|
||||
expect(screen.getByRole('heading', { name: '运行限制', exact: true })).toBeVisible();
|
||||
expect(screen.queryByText('管理我的云端能力', { exact: true })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('heading', { name: '费用上限', exact: true })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('heading', { name: '运行限制', exact: true })).not.toBeInTheDocument();
|
||||
expect(screen.queryByLabelText('工具审批')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('navigation', { name: '配置分类' })).not.toBeInTheDocument();
|
||||
expect(screen.getAllByRole('tab').map(tab => tab.textContent)).toEqual(['介绍与提示词', '高级设置']);
|
||||
|
||||
@@ -494,18 +499,12 @@ it('shows all advanced settings together and preserves unfinished selections, se
|
||||
const search = screen.getByLabelText('搜索工具箱');
|
||||
fireEvent.change(search, { target: { value: '文章' } });
|
||||
fireEvent.click(screen.getByRole('checkbox', { name: '文章整理工具', exact: true }));
|
||||
const steps = screen.getByLabelText('最多执行步数');
|
||||
fireEvent.change(steps, { target: { value: '70' } });
|
||||
const budget = screen.getByLabelText('每次任务上限(词元点数)');
|
||||
await waitFor(() => expect(budget).toBeEnabled());
|
||||
fireEvent.change(budget, { target: { value: '2.50' } });
|
||||
|
||||
fireEvent.mouseDown(screen.getByRole('tab', { name: '介绍与提示词' }), { button: 0, ctrlKey: false });
|
||||
const prompt = ' 保留我的原始写作要求。\n一次讨论一个想法。\n';
|
||||
fireEvent.change(screen.getByLabelText('提示词'), { target: { value: prompt } });
|
||||
expect(model).not.toBeVisible();
|
||||
expect(search).not.toBeVisible();
|
||||
expect(budget).not.toBeVisible();
|
||||
fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false });
|
||||
|
||||
expect(screen.getByLabelText('模型')).toBe(model);
|
||||
@@ -514,15 +513,12 @@ it('shows all advanced settings together and preserves unfinished selections, se
|
||||
expect(search).toHaveValue('文章');
|
||||
expect(screen.getByRole('checkbox', { name: '文章整理工具', exact: true })).toBeChecked();
|
||||
expect(screen.getByRole('checkbox', { name: '写作方法', exact: true })).toBeChecked();
|
||||
expect(steps).toHaveValue(70);
|
||||
expect(budget).toHaveValue(2.5);
|
||||
expect(screen.getByRole('button', { name: '保存费用上限', exact: true })).toBeEnabled();
|
||||
expect(screen.getByLabelText('提示词')).toHaveValue(prompt);
|
||||
const unload = new Event('beforeunload', { cancelable: true });
|
||||
window.dispatchEvent(unload);
|
||||
expect(unload.defaultPrevented).toBe(true);
|
||||
expect(api.save).not.toHaveBeenCalled();
|
||||
expect(api.call.mock.calls.some(([operation]) => ['saveBudget', 'publish', 'preview', 'submit'].includes(operation))).toBe(false);
|
||||
expect(api.call.mock.calls.some(([operation]) => ['budget', 'saveBudget', 'resources', 'publish', 'preview', 'submit'].includes(operation))).toBe(false);
|
||||
});
|
||||
|
||||
it('retains requirements when model loading fails and offers a usable retry', async () => {
|
||||
@@ -719,7 +715,7 @@ it('saves an existing configured draft without creating or loading a configurati
|
||||
expect(api.call.mock.calls.some(([operation]) => ['threads', 'history', 'preview', 'submit', 'publish'].includes(operation))).toBe(false);
|
||||
});
|
||||
|
||||
it('switches editor pages while retaining unfinished knowledge and budget form inputs', async () => {
|
||||
it('switches editor pages while retaining unfinished knowledge form inputs', async () => {
|
||||
api.list.mockResolvedValue({ agents: [{ ...draft, configuration: { ...EMPTY_CLOUD_CONFIGURATION, model: 'test-model' } }], next_cursor: null });
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
|
||||
@@ -744,22 +740,16 @@ it('switches editor pages while retaining unfinished knowledge and budget form i
|
||||
fireEvent.click(screen.getByRole('button', { name: '新建知识库' }));
|
||||
const knowledgeName = screen.getByLabelText('知识库名称');
|
||||
fireEvent.change(knowledgeName, { target: { value: '还没创建的资料' } });
|
||||
const budgetInput = screen.getByLabelText('每日上限(词元点数)');
|
||||
await waitFor(() => expect(budgetInput).toBeEnabled());
|
||||
fireEvent.change(budgetInput, { target: { value: '12.50' } });
|
||||
fireEvent.mouseDown(advanced, { button: 0, ctrlKey: false });
|
||||
expect(knowledgeName).toBeVisible();
|
||||
expect(budgetInput).toBeVisible();
|
||||
|
||||
fireEvent.mouseDown(requirements, { button: 0, ctrlKey: false });
|
||||
expect(screen.queryByRole('navigation', { name: '配置分类' })).not.toBeInTheDocument();
|
||||
expect(budgetInput).not.toBeVisible();
|
||||
expect(knowledgeName).not.toBeVisible();
|
||||
expect(screen.getByLabelText('提示词')).toBe(instructions);
|
||||
expect(instructions).toHaveValue('没有保存的要求');
|
||||
fireEvent.mouseDown(advanced, { button: 0, ctrlKey: false });
|
||||
expect(screen.queryByRole('navigation', { name: '配置分类' })).not.toBeInTheDocument();
|
||||
expect(screen.getByLabelText('每日上限(词元点数)')).toBe(budgetInput);
|
||||
expect(budgetInput).toHaveValue(12.5);
|
||||
expect(screen.getByLabelText('知识库名称')).toBe(knowledgeName);
|
||||
expect(knowledgeName).toHaveValue('还没创建的资料');
|
||||
expect(screen.queryByLabelText('消息')).not.toBeInTheDocument();
|
||||
@@ -767,27 +757,39 @@ it('switches editor pages while retaining unfinished knowledge and budget form i
|
||||
expect(api.call.mock.calls.some(([operation]) => ['saveBudget', 'preview', 'submit'].includes(operation))).toBe(false);
|
||||
});
|
||||
|
||||
it('retains unsaved instructions while saving a budget independently of the draft', async () => {
|
||||
const configured = { ...draft, configuration: { ...EMPTY_CLOUD_CONFIGURATION, model: 'test-model' } };
|
||||
it('preserves hidden runtime policies while saving model, prompt and capability changes without touching the budget', async () => {
|
||||
const configuration = { ...EMPTY_CLOUD_CONFIGURATION, model: 'test-model',
|
||||
tool_approval_mode: 'always_trust' as const, max_execution_steps: 70, max_output_tokens: 7200, max_run_seconds: 180 };
|
||||
const configured = { ...draft, configuration };
|
||||
api.list.mockResolvedValue({ agents: [configured], next_cursor: null });
|
||||
const base = api.call.getMockImplementation()!;
|
||||
api.call.mockImplementation(async (operation, input) => operation === 'saveBudget' ? { ...input, daily_committed_points: '0.00' } : base(operation, input));
|
||||
api.call.mockImplementation(async (operation, input) => operation === 'catalog' ? {
|
||||
models: [{ id: 'test-model', name: '原模型' }, { id: 'another-model', name: '新模型' }],
|
||||
resources: { tools: [{ key: 'writing-tool', name: '文章整理工具' }] }, pricing: null,
|
||||
} : base(operation, input));
|
||||
api.save.mockImplementation(async (_slug, input) => ({ ...configured, ...input, draft_revision: 2 }));
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
|
||||
fireEvent.change(screen.getByLabelText('提示词'), { target: { value: '未保存的指令' } });
|
||||
expect(screen.queryByLabelText('消息')).not.toBeInTheDocument();
|
||||
expect(screen.getByLabelText('提示词')).toHaveValue('未保存的指令');
|
||||
fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false });
|
||||
const budget = screen.getByLabelText('每日上限(词元点数)');
|
||||
await waitFor(() => expect(budget).toBeEnabled());
|
||||
fireEvent.change(budget, { target: { value: '12' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存费用上限' }));
|
||||
await waitFor(() => expect(api.call).toHaveBeenCalledWith('saveBudget', expect.objectContaining({ daily_limit_points: '12' })));
|
||||
await waitFor(() => expect(screen.getByLabelText('模型')).toBeEnabled());
|
||||
fireEvent.change(screen.getByLabelText('模型'), { target: { value: 'another-model' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '选择工具箱', exact: true }));
|
||||
fireEvent.click(screen.getByRole('checkbox', { name: '文章整理工具', exact: true }));
|
||||
expect(screen.queryByLabelText('最多执行步数')).not.toBeInTheDocument();
|
||||
expect(screen.queryByLabelText('每日上限(词元点数)')).not.toBeInTheDocument();
|
||||
expect(api.save).not.toHaveBeenCalled();
|
||||
fireEvent.mouseDown(screen.getByRole('tab', { name: '介绍与提示词', exact: true }), { button: 0, ctrlKey: false });
|
||||
expect(screen.getByLabelText('提示词')).toHaveValue('未保存的指令');
|
||||
expect(screen.getByRole('button', { name: '保存草稿' })).toBeEnabled();
|
||||
expect(api.call.mock.calls.some(([operation]) => ['threads', 'preview', 'submit'].includes(operation))).toBe(false);
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
|
||||
await screen.findByRole('button', { name: '已保存' });
|
||||
expect(api.save).toHaveBeenCalledWith(draft.slug, expect.objectContaining({
|
||||
system_prompt: '未保存的指令', configuration: { ...configuration, model: 'another-model', tools: ['writing-tool'] },
|
||||
}));
|
||||
expect(api.call.mock.calls.some(([operation]) => ['budget', 'saveBudget', 'resources', 'threads', 'preview', 'submit'].includes(operation))).toBe(false);
|
||||
});
|
||||
|
||||
it('saves a cleared model selection without choosing another model or starting a preview', async () => {
|
||||
|
||||
Reference in new issue
Block a user