Refine personal Agent cards and configuration
This commit is contained in:
1 parent
c14d996c05
commit
8b40463a9f
17 files changed
+555
-412
No files matched your search
@@ -27,6 +27,80 @@ it('shows a default avatar for absent or broken images and recovers when the ima
|
||||
expect(screen.getByRole('img', { name: '写作搭档头像' })).toHaveAttribute('src', 'https://images.test/new.png');
|
||||
});
|
||||
|
||||
it('keeps a new assistant portrait while its name is typed or cleared', () => {
|
||||
const view = render(<CloudAvatar name="" />);
|
||||
const portrait = screen.getByRole('img').getAttribute('src');
|
||||
for (const name of ['学', '学习', '学习 Friend', '学习 Friend 🚀', '']) {
|
||||
view.rerender(<CloudAvatar name={name} />);
|
||||
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
|
||||
expect(screen.getByRole('img')).toHaveAttribute('alt', `${name}默认头像`);
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps an assistant portrait across renaming, reopening and a failed custom image', () => {
|
||||
const view = render(<CloudAvatar identity="english-practice" name="英语陪练" />);
|
||||
const portrait = screen.getByRole('img').getAttribute('src');
|
||||
for (const name of ['英', '英语', '英语 Friend', '英语 Friend 🌟']) {
|
||||
view.rerender(<CloudAvatar identity="english-practice" name={name} />);
|
||||
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
|
||||
}
|
||||
view.unmount();
|
||||
const reopened = render(<CloudAvatar identity="english-practice" name="英语 Friend 🌟" src="https://images.test/saved.png" />);
|
||||
expect(screen.getByRole('img')).toHaveAttribute('src', 'https://images.test/saved.png');
|
||||
fireEvent.error(screen.getByRole('img'));
|
||||
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
|
||||
reopened.rerender(<CloudAvatar identity="english-practice" name="新名字" src="https://images.test/replaced.png" />);
|
||||
expect(screen.getByRole('img')).toHaveAttribute('src', 'https://images.test/replaced.png');
|
||||
});
|
||||
|
||||
it('uses one default portrait across creation, persisted identities and reopening', () => {
|
||||
const creation = render(<CloudAvatar name="" />);
|
||||
const portrait = screen.getByRole('img').getAttribute('src');
|
||||
creation.rerender(<CloudAvatar name="我的英语陪练" />);
|
||||
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
|
||||
creation.unmount();
|
||||
const view = render(<CloudAvatar identity="assistant-a" name="同名助手" />);
|
||||
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
|
||||
view.rerender(<CloudAvatar identity="assistant-b" name="同名助手" />);
|
||||
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
|
||||
view.rerender(<CloudAvatar identity="assistant-a" name="改名后的助手" />);
|
||||
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
|
||||
view.unmount();
|
||||
render(<CloudAvatar identity="assistant-a" name="改名后的助手" />);
|
||||
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
|
||||
});
|
||||
|
||||
it('keeps the default selection and chosen portrait stable while the name is edited', async () => {
|
||||
api.useAvatarPreset.mockResolvedValue({ icon: '/chosen.png', icon_url: 'https://images.test/chosen.png' });
|
||||
function RenameEditor() {
|
||||
const [name, setName] = useState('英语陪练');
|
||||
const [value, setValue] = useState<CloudAgentAvatar>({});
|
||||
const [uploading, setUploading] = useState(false);
|
||||
return <><input aria-label="助手名称" value={name} onChange={event => setName(event.target.value)} />
|
||||
<CloudAvatarEditor identity="english-practice" name={name} value={value} disabled={false} uploading={uploading} onUploading={setUploading} onChange={setValue} /></>;
|
||||
}
|
||||
render(<RenameEditor />);
|
||||
const portraits = () => screen.getAllByRole('button', { name: /^像素伙伴 / });
|
||||
const initialSelection = portraits().find(button => button.getAttribute('aria-pressed') === 'true')!;
|
||||
const portrait = screen.getByRole('img', { name: '英语陪练默认头像' }).getAttribute('src');
|
||||
expect(initialSelection.querySelector('img')).toHaveAttribute('src', portrait);
|
||||
for (const name of ['英', '英语', '英语 Friend']) {
|
||||
fireEvent.change(screen.getByRole('textbox', { name: '助手名称' }), { target: { value: name } });
|
||||
expect(screen.getByRole('img', { name: `${name}默认头像` })).toHaveAttribute('src', portrait);
|
||||
expect(initialSelection).toHaveAttribute('aria-pressed', 'true');
|
||||
expect(portraits().filter(button => button.getAttribute('aria-pressed') === 'true')).toHaveLength(1);
|
||||
}
|
||||
fireEvent.click(screen.getByRole('button', { name: '像素伙伴 7' }));
|
||||
await waitFor(() => expect(screen.getByRole('img', { name: '英语 Friend头像' })).toHaveAttribute('src', 'https://images.test/chosen.png'));
|
||||
fireEvent.change(screen.getByRole('textbox', { name: '助手名称' }), { target: { value: '新的伙伴' } });
|
||||
expect(screen.getByRole('img', { name: '新的伙伴头像' })).toHaveAttribute('src', 'https://images.test/chosen.png');
|
||||
expect(screen.getByRole('button', { name: '像素伙伴 7' })).toHaveAttribute('aria-pressed', 'true');
|
||||
fireEvent.click(screen.getByRole('button', { name: '移除头像' }));
|
||||
expect(screen.getByRole('img', { name: '新的伙伴默认头像' })).toHaveAttribute('src', portrait);
|
||||
expect(initialSelection).toHaveAttribute('aria-pressed', 'true');
|
||||
expect(api.useAvatarPreset).toHaveBeenCalledExactlyOnceWith('avatar-07');
|
||||
});
|
||||
|
||||
it('offers sixteen platform portraits and uses the chosen portrait without a file picker', async () => {
|
||||
api.useAvatarPreset.mockResolvedValue({ icon: '/chosen.png', icon_url: 'https://images.test/chosen.png' });
|
||||
render(<Editor />);
|
||||
|
||||
@@ -44,6 +44,26 @@ function moduleFor(fetchImpl: typeof fetch) {
|
||||
beforeEach(() => { vi.clearAllMocks(); journalRecords.clear(); clearWorksSquareSession(); login(); });
|
||||
afterEach(() => { instances.splice(0).forEach((module) => module.dispose()); clearWorksSquareSession(); vi.useRealTimers(); });
|
||||
|
||||
it.each(['', ' '])('preserves an empty introduction through create, save and list (%j)', async purpose => {
|
||||
const emptyDraft = { ...draft, purpose: '' };
|
||||
const transport = vi.fn().mockResolvedValueOnce(session()).mockResolvedValueOnce(json(emptyDraft))
|
||||
.mockResolvedValueOnce(json({ ...emptyDraft, draft_revision: 2 })).mockResolvedValueOnce(json({ agents: [emptyDraft], next_cursor: null }));
|
||||
const module = moduleFor(transport);
|
||||
expect(await module.create({ ...input, purpose })).toMatchObject({ purpose: '' });
|
||||
expect(await module.save(draft.slug, { expected_revision: 1, name: draft.name, purpose, system_prompt: draft.system_prompt })).toMatchObject({ purpose: '' });
|
||||
expect((await module.list()).agents[0].purpose).toBe('');
|
||||
expect(JSON.parse(transport.mock.calls[1][1].body).purpose).toBe('');
|
||||
expect(JSON.parse(transport.mock.calls[2][1].body).purpose).toBe('');
|
||||
});
|
||||
|
||||
it.each([null, 42, 'x'.repeat(2001)])('rejects malformed or overlong introductions before network access', async purpose => {
|
||||
const transport = vi.fn();
|
||||
const module = moduleFor(transport);
|
||||
await expect(module.create({ ...input, purpose })).rejects.toMatchObject({ code: 'invalid_input' });
|
||||
await expect(module.save(draft.slug, { expected_revision: 1, name: draft.name, purpose, system_prompt: draft.system_prompt })).rejects.toMatchObject({ code: 'invalid_input' });
|
||||
expect(transport).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('Main cloud Agents boundary', () => {
|
||||
it.each(Array.from({ length: 16 }, (_, index) => `avatar-${String(index + 1).padStart(2, '0')}`))('uploads the bundled platform portrait %s through scoped Main transport without a dialog', async id => {
|
||||
const icon = '/api/storage/public/images/creator/preset.png';
|
||||
|
||||
@@ -36,14 +36,15 @@ beforeEach(() => {
|
||||
});
|
||||
afterEach(cleanup);
|
||||
|
||||
it('does not label disabled or archived assistants as ready to use', async () => {
|
||||
it('does not label disabled assistants as ready to use or show archived assistants', 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();
|
||||
expect(screen.queryByRole('article', { name: '归档助手' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('可以使用')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -83,20 +84,25 @@ it('shows saved but unapplied changes and applies directly from the editor heade
|
||||
['publish', expect.objectContaining({ slug: draft.slug, expected_revision: 4 })],
|
||||
]);
|
||||
expect(screen.queryByText('修改已保存,尚未应用')).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '配置', exact: true })).toHaveAttribute('aria-current', 'page');
|
||||
expect(screen.getByRole('tab', { name: '介绍与提示词' })).toHaveAttribute('aria-selected', 'true');
|
||||
});
|
||||
|
||||
it('requires saving before applying and preserves formal chat input across configuration and application', async () => {
|
||||
it('keeps unsent input accessible before adjusting and applying configuration', async () => {
|
||||
publishedAssistant(4);
|
||||
api.recovery.mockResolvedValue({ recent: null, pending: [] });
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: '和它聊聊', exact: true }));
|
||||
await screen.findByTestId('cloud-agent-editor');
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '已应用', exact: true })).toBeDisabled());
|
||||
expect(screen.queryByLabelText('消息')).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '聊天', exact: true }));
|
||||
const message = await screen.findByLabelText('消息');
|
||||
await waitFor(() => expect(message).toBeEnabled());
|
||||
fireEvent.change(message, { target: { value: '保留正式聊天的问题' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '配置', exact: true }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '调整智能体', exact: true }));
|
||||
expect(message).toBeVisible();
|
||||
expect(message).toHaveValue('保留正式聊天的问题');
|
||||
expect(screen.getByText('对话中还有未完成的内容,请先发送或处理后再调整智能体。')).toBeVisible();
|
||||
fireEvent.change(message, { target: { value: '' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '调整智能体', exact: true }));
|
||||
expect(message).not.toBeVisible();
|
||||
fireEvent.change(screen.getByLabelText('提示词'), { target: { value: '新的要求' } });
|
||||
expect(screen.getByText('修改尚未保存和应用')).toBeVisible();
|
||||
@@ -106,15 +112,16 @@ it('requires saving before applying and preserves formal chat input across confi
|
||||
expect(api.call.mock.calls.some(([operation]) => operation === 'publish')).toBe(false);
|
||||
fireEvent.click(screen.getByRole('button', { name: '应用最新修改', exact: true }));
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '已应用', exact: true })).toBeDisabled());
|
||||
expect(screen.getByRole('button', { name: '配置', exact: true })).toHaveAttribute('aria-current', 'page');
|
||||
fireEvent.click(screen.getByRole('button', { name: '聊天', exact: true }));
|
||||
expect(screen.getByRole('tab', { name: '介绍与提示词' })).toHaveAttribute('aria-selected', 'true');
|
||||
expect(screen.getByLabelText('消息')).toBe(message);
|
||||
expect(message).toHaveValue('保留正式聊天的问题');
|
||||
expect(message).not.toBeVisible();
|
||||
expect(message).toHaveValue('');
|
||||
expect(screen.queryByRole('button', { name: '聊天', exact: true })).not.toBeInTheDocument();
|
||||
expect(api.call.mock.calls.filter(([operation]) => operation === 'publish')[0][1]).toMatchObject({ expected_revision: 5 });
|
||||
expect(api.call.mock.calls.some(([operation]) => operation === 'preview' || operation === 'submit')).toBe(false);
|
||||
});
|
||||
|
||||
it('shares the original failed publication between the header and access page', async () => {
|
||||
it('retries a failed application from the header using the original publication intent', async () => {
|
||||
publishedAssistant();
|
||||
const base = api.call.getMockImplementation()!;
|
||||
let attempts = 0;
|
||||
@@ -128,8 +135,7 @@ it('shares the original failed publication between the header and access page',
|
||||
fireEvent.click(screen.getByRole('button', { name: '应用最新修改', exact: true }));
|
||||
await screen.findByText(/连接中断/);
|
||||
expect(screen.getByRole('button', { name: '重试本次应用' })).toBeEnabled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '使用与接入' }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: '重试本次发布' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '重试本次应用' }));
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '已应用', exact: true })).toBeDisabled());
|
||||
const calls = api.call.mock.calls.filter(([operation]) => operation === 'publish');
|
||||
expect(calls).toHaveLength(2);
|
||||
@@ -192,7 +198,7 @@ it('protects an uploaded avatar before a name has been entered', async () => {
|
||||
expect(api.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates from name and avatar, then configures model and raw prompt without a preview chat', async () => {
|
||||
it('creates from name and avatar, then configures an optional introduction, advanced model and raw prompt', async () => {
|
||||
const avatar = { icon: '/api/storage/public/images/avatar.png', icon_url: 'https://images.test/avatar.png' };
|
||||
const base = api.call.getMockImplementation()!;
|
||||
api.call.mockImplementation(async (operation, input) => operation === 'catalog' ? {
|
||||
@@ -222,9 +228,14 @@ it('creates from name and avatar, then configures model and raw prompt without a
|
||||
await screen.findByTestId('cloud-agent-editor');
|
||||
expect(screen.getByLabelText('名称')).toHaveValue('我的写作助手');
|
||||
expect(screen.getByRole('img', { name: '我的写作助手头像' })).toHaveAttribute('src', avatar.icon_url);
|
||||
expect(api.create).toHaveBeenCalledWith({ operation_id: expect.any(String), name: '我的写作助手', purpose: '按照我的提示词提供帮助。', icon: avatar.icon });
|
||||
expect(api.create).toHaveBeenCalledWith({ operation_id: expect.any(String), name: '我的写作助手', purpose: '', icon: avatar.icon });
|
||||
expect(api.create.mock.calls[0][0]).not.toHaveProperty('icon_url');
|
||||
await waitFor(() => expect(screen.getByLabelText('模型')).toHaveValue('model-first'));
|
||||
await waitFor(() => expect(screen.getByLabelText('模型')).toBeEnabled());
|
||||
expect(screen.getByLabelText('一句话介绍')).toHaveValue('');
|
||||
expect(screen.getByLabelText('一句话介绍')).toBeVisible();
|
||||
expect(screen.getByLabelText('模型')).toHaveValue('');
|
||||
expect(screen.getByLabelText('模型')).not.toBeVisible();
|
||||
expect(screen.getByRole('tab', { name: '介绍与提示词' })).toHaveAttribute('aria-selected', 'true');
|
||||
expect(screen.getByLabelText('提示词')).toHaveValue('');
|
||||
expect(screen.queryByLabelText('用途')).not.toBeInTheDocument();
|
||||
expect(screen.queryByLabelText('消息')).not.toBeInTheDocument();
|
||||
@@ -232,17 +243,24 @@ it('creates from name and avatar, then configures model and raw prompt without a
|
||||
expect(screen.queryByText(/这是帮助它思考和回答的 AI|新助手从目录中的第一个可用模型开始|选择模型后,保存即可试用/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/每 .*个词元约 .*词元点数/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /简单易懂|一步一步来|先给我提示/ })).not.toBeInTheDocument();
|
||||
fireEvent.change(screen.getByLabelText('模型'), { target: { value: '' } });
|
||||
for (const name of ['聊天', '自动任务', '使用与接入']) {
|
||||
expect(screen.queryByRole('button', { name, exact: true })).not.toBeInTheDocument();
|
||||
}
|
||||
fireEvent.change(screen.getByLabelText('名称'), { target: { value: '我的文章搭档' } });
|
||||
expect(screen.getByLabelText('模型')).toHaveValue('');
|
||||
fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false });
|
||||
expect(screen.getByRole('button', { name: '模型', exact: true })).toHaveAttribute('aria-current', 'page');
|
||||
expect(screen.getByLabelText('模型')).toBeVisible();
|
||||
expect(screen.getByLabelText('模型')).toBeRequired();
|
||||
fireEvent.change(screen.getByLabelText('模型'), { target: { value: 'model-second' } });
|
||||
fireEvent.mouseDown(screen.getByRole('tab', { name: '介绍与提示词' }), { button: 0, ctrlKey: false });
|
||||
const prompt = '一次只解释一个新词。\n\n 保留缩进。\n使用简单易懂的语言,遇到新词先解释。\n';
|
||||
fireEvent.change(screen.getByLabelText('提示词'), { target: { value: prompt } });
|
||||
expect(api.save).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
|
||||
await screen.findByRole('button', { name: '已保存' });
|
||||
expect(api.save).toHaveBeenCalledWith(draft.slug, expect.objectContaining({
|
||||
name: '我的文章搭档', system_prompt: prompt, icon: avatar.icon,
|
||||
name: '我的文章搭档', purpose: '', system_prompt: prompt, icon: avatar.icon,
|
||||
configuration: expect.objectContaining({ model: 'model-second' }),
|
||||
}));
|
||||
expect(api.save.mock.calls[0][1]).not.toHaveProperty('icon_url');
|
||||
@@ -341,22 +359,101 @@ it('treats an avatar change as a manual draft edit and blocks saving during its
|
||||
|
||||
it('saves renamed existing assistants without rewriting arbitrary instructions or selecting a model', async () => {
|
||||
const original = '自定义标题\n\n 保留缩进。\n使用简单易懂的语言,遇到新词先解释。\n把任务拆成小步骤,一次引导我完成一步。\n先给我提示和思考问题,让我尝试后再补充答案。\n';
|
||||
const existing = { ...draft, system_prompt: original };
|
||||
api.list.mockResolvedValue({ agents: [existing], next_cursor: null });
|
||||
let existing = { ...draft, system_prompt: original };
|
||||
api.list.mockImplementation(async () => ({ 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 }));
|
||||
api.save.mockImplementation(async (_slug, input) => (existing = { ...existing, ...input, draft_revision: 2 }));
|
||||
open();
|
||||
const cardAvatar = await screen.findByRole('img', { name: '写作搭档默认头像' });
|
||||
const portrait = cardAvatar.getAttribute('src');
|
||||
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
|
||||
await waitFor(() => expect(screen.getByLabelText('模型')).toBeEnabled());
|
||||
expect(screen.getByLabelText('模型')).toHaveValue('');
|
||||
expect(screen.getByLabelText('模型')).not.toBeVisible();
|
||||
expect(screen.getByLabelText('提示词')).toHaveValue(original);
|
||||
fireEvent.click(screen.getByRole('button', { name: '修改名称' }));
|
||||
expect(screen.getByLabelText('名称')).toHaveFocus();
|
||||
for (const name of ['新', '新名', '新名字']) {
|
||||
fireEvent.change(screen.getByLabelText('名称'), { target: { value: name } });
|
||||
expect(screen.getByRole('img', { name: `${name}默认头像` })).toHaveAttribute('src', portrait);
|
||||
}
|
||||
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: '' }) }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '我的智能体', exact: true }));
|
||||
expect(await screen.findByRole('article', { name: '新名字' })).toBeVisible();
|
||||
expect(screen.getByRole('img', { name: '新名字默认头像' })).toHaveAttribute('src', portrait);
|
||||
fireEvent.click(screen.getByRole('button', { name: '继续制作', exact: true }));
|
||||
expect(screen.getByRole('img', { name: '新名字默认头像' })).toHaveAttribute('src', portrait);
|
||||
expect(screen.getByLabelText('提示词')).toHaveValue(original);
|
||||
});
|
||||
|
||||
it('uses the saved introduction on the assistant card and permits clearing it on reopening', async () => {
|
||||
let current = { ...draft };
|
||||
api.list.mockImplementation(async () => ({ agents: [current], next_cursor: null }));
|
||||
api.save.mockImplementation(async (_slug, input) => (current = { ...current, ...input, draft_revision: current.draft_revision + 1 }));
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
|
||||
const introduction = '和我一起把小想法变成故事';
|
||||
fireEvent.change(screen.getByLabelText('一句话介绍'), { target: { value: introduction } });
|
||||
expect(api.save).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
|
||||
await screen.findByRole('button', { name: '已保存' });
|
||||
expect(api.save).toHaveBeenCalledWith(draft.slug, expect.objectContaining({ purpose: introduction, system_prompt: '原始指令' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '我的智能体', exact: true }));
|
||||
expect(await screen.findByText(introduction)).toBeVisible();
|
||||
fireEvent.click(screen.getByRole('button', { name: '继续制作', exact: true }));
|
||||
expect(screen.getByLabelText('一句话介绍')).toHaveValue(introduction);
|
||||
fireEvent.change(screen.getByLabelText('一句话介绍'), { target: { value: '' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
|
||||
await screen.findByRole('button', { name: '已保存' });
|
||||
expect(api.save.mock.calls[1][1]).toMatchObject({ purpose: '', system_prompt: '原始指令' });
|
||||
fireEvent.click(screen.getByRole('button', { name: '我的智能体', exact: true }));
|
||||
const card = await screen.findByRole('article', { name: '写作搭档' });
|
||||
expect(within(card).queryByText(introduction)).not.toBeInTheDocument();
|
||||
expect(card.querySelector('.agent-home-purpose')).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '继续制作', exact: true }));
|
||||
expect(screen.getByLabelText('一句话介绍')).toHaveValue('');
|
||||
});
|
||||
|
||||
it('requires an explicit advanced model choice before starting use but permits an unfinished draft save', async () => {
|
||||
let current = { ...draft };
|
||||
const base = api.call.getMockImplementation()!;
|
||||
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: [] };
|
||||
return base(operation, input);
|
||||
});
|
||||
api.save.mockImplementation(async (_slug, input) => (current = { ...current, ...input, draft_revision: current.draft_revision + 1 }));
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
|
||||
await waitFor(() => expect(screen.getByLabelText('模型')).toBeEnabled());
|
||||
expect(screen.getByLabelText('模型')).toHaveValue('');
|
||||
expect(screen.getByLabelText('提示词')).toHaveValue(original);
|
||||
fireEvent.change(screen.getByLabelText('名称'), { target: { value: '新名字' } });
|
||||
expect(screen.getByLabelText('模型')).not.toBeVisible();
|
||||
expect(api.save).not.toHaveBeenCalled();
|
||||
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: '' }) }));
|
||||
expect(api.save).toHaveBeenCalledWith(draft.slug, expect.objectContaining({ configuration: expect.objectContaining({ model: '' }) }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '开始使用', exact: true }));
|
||||
expect(screen.getByRole('tab', { name: '高级设置' })).toHaveAttribute('aria-selected', 'true');
|
||||
const categories = within(screen.getByRole('navigation', { name: '配置分类' })).getAllByRole('button');
|
||||
expect(categories[0]).toHaveAccessibleName('模型');
|
||||
expect(categories[0]).toHaveAttribute('aria-current', 'page');
|
||||
expect(screen.getByLabelText('模型')).toBeVisible();
|
||||
expect(screen.getByLabelText('模型')).toBeRequired();
|
||||
expect(screen.getByRole('alert')).toHaveTextContent('请先在高级设置中选择模型。');
|
||||
expect(screen.queryByRole('button', { name: '确认开始使用' })).not.toBeInTheDocument();
|
||||
expect(api.call.mock.calls.some(([operation]) => operation === 'publish')).toBe(false);
|
||||
fireEvent.change(screen.getByLabelText('模型'), { target: { value: 'model-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);
|
||||
});
|
||||
|
||||
it('retains requirements when model loading fails and offers a usable retry', async () => {
|
||||
@@ -373,6 +470,8 @@ it('retains requirements when model loading fails and offers a usable retry', as
|
||||
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
|
||||
await screen.findByText('目录暂不可用');
|
||||
fireEvent.change(screen.getByLabelText('提示词'), { target: { value: '保留我的想法' } });
|
||||
fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false });
|
||||
expect(screen.getByText('目录暂不可用')).toBeVisible();
|
||||
fail = false;
|
||||
fireEvent.click(screen.getByRole('button', { name: '重新读取模型' }));
|
||||
await waitFor(() => expect(screen.getByLabelText('模型')).toBeEnabled());
|
||||
@@ -467,7 +566,7 @@ it('freezes the uploaded avatar and creation identity when retrying a lost respo
|
||||
await screen.findByTestId('cloud-agent-editor');
|
||||
expect(api.create).toHaveBeenCalledTimes(2);
|
||||
expect(api.create.mock.calls[1][0]).toEqual(api.create.mock.calls[0][0]);
|
||||
expect(api.create.mock.calls[0][0]).toEqual({ operation_id: expect.any(String), name: '写作搭档', purpose: '按照我的提示词提供帮助。', icon: avatar.icon });
|
||||
expect(api.create.mock.calls[0][0]).toEqual({ operation_id: expect.any(String), name: '写作搭档', purpose: '', icon: avatar.icon });
|
||||
expect(api.uploadAvatar).toHaveBeenCalledTimes(1);
|
||||
expect(screen.getByRole('img', { name: '写作搭档头像' })).toHaveAttribute('src', avatar.icon_url);
|
||||
});
|
||||
@@ -556,11 +655,11 @@ it('switches peer editor tabs while retaining the advanced category and unfinish
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
|
||||
await waitFor(() => expect(screen.getByLabelText('模型')).toBeEnabled());
|
||||
const requirements = screen.getByRole('tab', { name: '模型与提示词', exact: true });
|
||||
const requirements = screen.getByRole('tab', { name: '介绍与提示词', exact: true });
|
||||
const advanced = screen.getByRole('tab', { name: '高级设置', exact: true });
|
||||
expect(requirements).toHaveAttribute('aria-selected', 'true');
|
||||
expect(advanced).toHaveAttribute('aria-selected', 'false');
|
||||
expect(screen.getByRole('tabpanel', { name: '模型与提示词' })).toBeVisible();
|
||||
expect(screen.getByRole('tabpanel', { name: '介绍与提示词' })).toBeVisible();
|
||||
expect(screen.queryByRole('navigation', { name: '配置分类' })).not.toBeInTheDocument();
|
||||
const instructions = screen.getByLabelText('提示词');
|
||||
fireEvent.change(instructions, { target: { value: '没有保存的要求' } });
|
||||
@@ -570,7 +669,7 @@ it('switches peer editor tabs while retaining the advanced category and unfinish
|
||||
expect(requirements).toHaveAttribute('aria-selected', 'false');
|
||||
expect(screen.getByRole('tabpanel', { name: '高级设置' })).toBeVisible();
|
||||
expect(instructions).not.toBeVisible();
|
||||
expect(screen.getByRole('button', { name: '能力', exact: true })).toHaveAttribute('aria-current', 'page');
|
||||
expect(screen.getByRole('button', { name: '模型', exact: true })).toHaveAttribute('aria-current', 'page');
|
||||
fireEvent.click(screen.getByRole('button', { name: '知识', exact: true }));
|
||||
fireEvent.click(screen.getByText('管理我的知识文档'));
|
||||
fireEvent.click(screen.getByRole('button', { name: '新建知识库' }));
|
||||
@@ -609,9 +708,7 @@ it('retains unsaved instructions while saving a budget independently of the draf
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
|
||||
fireEvent.change(screen.getByLabelText('提示词'), { target: { value: '未保存的指令' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '聊天', exact: true }));
|
||||
expect(screen.queryByLabelText('消息')).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '配置', exact: true }));
|
||||
expect(screen.getByLabelText('提示词')).toHaveValue('未保存的指令');
|
||||
fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false });
|
||||
fireEvent.click(screen.getByRole('button', { name: '限制', exact: true }));
|
||||
@@ -621,7 +718,7 @@ it('retains unsaved instructions while saving a budget independently of the draf
|
||||
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.mouseDown(screen.getByRole('tab', { name: '模型与提示词', exact: true }), { button: 0, ctrlKey: false });
|
||||
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);
|
||||
@@ -662,7 +759,7 @@ it('searches capabilities while retaining existing selections outside the result
|
||||
expect(await screen.findByRole('checkbox', { name: /missing-tool/ })).toBeChecked();
|
||||
fireEvent.click(screen.getByRole('button', { name: '选择工具', exact: true }));
|
||||
fireEvent.change(screen.getByLabelText('搜索工具'), { target: { value: '资料' } });
|
||||
fireEvent.mouseDown(screen.getByRole('tab', { name: '模型与提示词' }), { button: 0, ctrlKey: false });
|
||||
fireEvent.mouseDown(screen.getByRole('tab', { name: '介绍与提示词' }), { button: 0, ctrlKey: false });
|
||||
fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false });
|
||||
expect(screen.getByLabelText('搜索工具')).toHaveValue('资料');
|
||||
expect(screen.queryByRole('checkbox', { name: '当前时间' })).not.toBeInTheDocument();
|
||||
|
||||
Reference in new issue
Block a user