fix(home): align stacked module entries with creation title
This commit is contained in:
1 parent
45e6b91715
commit
383ce71faf
11 files changed
+719
-110
No files matched your search
@@ -575,9 +575,13 @@ it('restores the exact previous thread and presents an uncertain operation witho
|
||||
expect(screen.queryByLabelText('历史对话')).not.toBeInTheDocument();
|
||||
expect(api.resolvePending).not.toHaveBeenCalled();
|
||||
expect(api.call.mock.calls.some(call => call[0] === 'submit')).toBe(false);
|
||||
expect(screen.queryByRole('button', { name: /^待处理记录/ })).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '返回智能体列表', exact: true }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: /^待处理记录/ }));
|
||||
await screen.findByRole('dialog', { name: '待处理记录' });
|
||||
api.resolvePending.mockResolvedValue({ result: { request_id: 'same-request', status: 'queued' } });
|
||||
api.recovery.mockResolvedValue({ recent: null, pending: [] });
|
||||
fireEvent.click(screen.getByRole('button', { name: '重试并确认结果' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '再试一次' }));
|
||||
await screen.findByText('发送消息的结果已确认。');
|
||||
expect(api.resolvePending).toHaveBeenCalledWith('submit:pending', false);
|
||||
});
|
||||
@@ -605,7 +609,8 @@ it('guides an unconfirmed WeChat verification back to its code entry without cla
|
||||
input: { slug: draft.slug, session_key: 'qr-session', operation_id: 'verify-operation' }, created_at: '2026-09-13T01:00:00Z' }] });
|
||||
api.resolvePending.mockResolvedValue({ requires_input: true, operation: 'wechatBindVerification' });
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: '重试并确认结果' }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: /^待处理记录/ }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: '再试一次' }));
|
||||
await screen.findByText(/这次微信验证还需要验证码/);
|
||||
expect(screen.queryByText('微信连接验证的结果已确认。')).not.toBeInTheDocument();
|
||||
expect(api.resolvePending).toHaveBeenCalledWith('wechat-code', false);
|
||||
@@ -616,11 +621,12 @@ it('refreshes recovery after a definitive rejection without presenting a confirm
|
||||
api.recovery.mockResolvedValueOnce({ recent: null, pending: [pending] }).mockResolvedValue({ recent: null, pending: [] });
|
||||
api.resolvePending.mockRejectedValue(new AppError('CONFIG', '名称不可用', undefined, { commandOutcome: 'definitive_failure' }));
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: '重试并确认结果' }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: /^创建记录/ }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: '再试一次' }));
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent('创建智能体失败:名称不可用');
|
||||
await waitFor(() => expect(api.recovery).toHaveBeenCalledTimes(2));
|
||||
expect(screen.queryByRole('button', { name: '重试并确认结果' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('创建智能体的结果已确认。')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '再试一次' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/创建成功,可以回到列表继续设置/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('查看结果')).not.toBeInTheDocument();
|
||||
expect(api.resolvePending).toHaveBeenCalledWith(pending.id, false);
|
||||
expect(api.create).not.toHaveBeenCalled();
|
||||
@@ -631,12 +637,13 @@ it('keeps an old recovery record retryable when a parameter response cannot dete
|
||||
api.recovery.mockResolvedValue({ recent: null, pending: [pending] });
|
||||
api.resolvePending.mockRejectedValue(new AppError('CONFIG', '请检查名称', undefined, { commandOutcome: 'unknown', status: 422, code: 'invalid_input' }));
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: '重试并确认结果' }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: /^创建记录/ }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: '再试一次' }));
|
||||
await screen.findByRole('alert');
|
||||
expect(screen.getByRole('button', { name: '重试并确认结果' })).toBeEnabled();
|
||||
expect(screen.queryByText('创建智能体的结果已确认。')).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '再试一次' })).toBeEnabled();
|
||||
expect(screen.queryByText(/创建成功,可以回到列表继续设置/)).not.toBeInTheDocument();
|
||||
expect(api.recovery).toHaveBeenCalledTimes(1);
|
||||
fireEvent.click(screen.getByRole('button', { name: '重试并确认结果' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '再试一次' }));
|
||||
await waitFor(() => expect(api.resolvePending).toHaveBeenCalledTimes(2));
|
||||
expect(api.resolvePending.mock.calls).toEqual([[pending.id, false], [pending.id, false]]);
|
||||
expect(api.create).not.toHaveBeenCalled();
|
||||
@@ -648,13 +655,84 @@ it('offers only explicit discard for a recovery record whose channel operation w
|
||||
.mockResolvedValue({ recent: null, pending: [] });
|
||||
api.resolvePending.mockResolvedValue({ operation: 'channelPolicy', discarded: true, retired: true });
|
||||
open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: /^待处理记录/ }));
|
||||
expect(await screen.findByText(/旧版联系人权限操作(已移除)/)).toBeVisible();
|
||||
expect(screen.queryByRole('button', { name: '重试并确认结果' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: '再试一次' })).toBeNull();
|
||||
fireEvent.click(screen.getByRole('button', { name: '丢弃旧记录' }));
|
||||
await screen.findByText(/旧版联系人权限操作已移除/);
|
||||
expect(api.resolvePending).toHaveBeenCalledWith('channel-policy-old', false);
|
||||
});
|
||||
|
||||
it('keeps historic creation records out of the new form while showing the current creation error inline', async () => {
|
||||
api.recovery.mockResolvedValue({ recent: null, pending: [{ id: 'create:old', operation: 'createAgent',
|
||||
input: { operation_id: 'original-old', name: '之前的失败创建' }, created_at: '2026-10-13T03:00:00Z' }] });
|
||||
api.create.mockRejectedValue(new Error('本次创建失败,请重新检查'));
|
||||
open();
|
||||
expect(await screen.findByRole('button', { name: /^创建记录/ })).toHaveTextContent('1');
|
||||
expect(screen.queryByRole('article', { name: '创建「之前的失败创建」' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('complementary', { name: '未确认的云端操作' })).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '创建智能体', exact: true }));
|
||||
expect(screen.queryByRole('button', { name: /^创建记录/ })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('dialog', { name: '创建记录' })).not.toBeInTheDocument();
|
||||
fireEvent.change(screen.getByLabelText('名称'), { target: { value: '新的搭档' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '下一步', exact: true }));
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent('本次创建失败,请重新检查');
|
||||
expect(screen.queryByRole('article', { name: '创建「之前的失败创建」' })).not.toBeInTheDocument();
|
||||
expect(api.resolvePending).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('places the records entry only in the list header and preserves it when returning from configuration or chat', async () => {
|
||||
publishedAssistant(4);
|
||||
api.recovery.mockResolvedValue({ recent: null, pending: [{ id: 'create:historic', operation: 'createAgent',
|
||||
input: { operation_id: 'same-historic', name: '旧记录搭档' }, created_at: '2026-10-13T03:00:00Z' }] });
|
||||
open();
|
||||
const trigger = await screen.findByRole('button', { name: /^创建记录/ });
|
||||
expect(trigger.closest('header')).not.toBeNull();
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '调整', exact: true }));
|
||||
await screen.findByTestId('cloud-agent-editor');
|
||||
expect(screen.queryByRole('button', { name: /^创建记录/ })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('complementary', { name: '未确认的云端操作' })).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '我的智能体', exact: true }));
|
||||
expect(await screen.findByRole('button', { name: /^创建记录/ })).toHaveTextContent('1');
|
||||
fireEvent.click(screen.getByRole('button', { name: '和它聊聊', exact: true }));
|
||||
await screen.findByLabelText('消息');
|
||||
expect(screen.queryByRole('button', { name: /^创建记录/ })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/有 1 个操作的结果尚未确认/)).not.toBeInTheDocument();
|
||||
expect(api.resolvePending).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('retains an unclosed key through internal configuration navigation and clears it across account changes', async () => {
|
||||
publishedAssistant(4);
|
||||
const secret = 'configuration-navigation-key-fixture';
|
||||
const pending = { id: 'key:historical', operation: 'createKey', input: { application_id: 'fixture-application', operation_id: 'original-key' }, created_at: '2026-10-13T03:00:00Z' };
|
||||
api.recovery.mockResolvedValue({ recent: null, pending: [pending] });
|
||||
api.resolvePending.mockImplementation(async () => {
|
||||
api.recovery.mockResolvedValue({ recent: null, pending: [] });
|
||||
return { result: { secret } };
|
||||
});
|
||||
const view = open();
|
||||
fireEvent.click(await screen.findByRole('button', { name: /^待处理记录/ }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '再试一次' }));
|
||||
await screen.findByText(new RegExp(secret));
|
||||
fireEvent.click(screen.getByRole('button', { name: '关闭记录' }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: '调整', exact: true }));
|
||||
await screen.findByTestId('cloud-agent-editor');
|
||||
expect(screen.queryByRole('button', { name: /^待处理记录/ })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('dialog', { name: '待处理记录' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(new RegExp(secret))).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '我的智能体', exact: true }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: /^待处理记录/ }));
|
||||
expect(await screen.findByText(new RegExp(secret))).toBeVisible();
|
||||
expect(api.resolvePending).toHaveBeenCalledTimes(1);
|
||||
fireEvent.click(screen.getByRole('button', { name: '关闭记录' }));
|
||||
auth.user.userId = 'different-records-account';
|
||||
view.rerender(<RouterProvider key="different-records-account" router={view.router} />);
|
||||
await screen.findByRole('button', { name: '调整', exact: true });
|
||||
expect(screen.queryByRole('button', { name: /^待处理记录/ })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(new RegExp(secret))).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('freezes the uploaded avatar and creation identity when retrying a lost response', async () => {
|
||||
const avatar = { icon: '/api/storage/public/images/create.png', icon_url: 'https://images.test/create.png' };
|
||||
api.list.mockResolvedValue({ agents: [], next_cursor: null });
|
||||
@@ -730,15 +808,18 @@ it('preserves the creation identity for unknown command outcomes even when the e
|
||||
await screen.findByRole('button', { name: '更换头像' });
|
||||
fireEvent.click(screen.getByRole('button', { name: '下一步', exact: true }));
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent('创建结果暂未确认,可重试确认结果');
|
||||
await screen.findByText('有 1 个操作的结果尚未确认');
|
||||
await waitFor(() => expect(api.recovery).toHaveBeenCalledTimes(2));
|
||||
expect(retainedPending).toHaveLength(1);
|
||||
expect(screen.queryByRole('button', { name: /^创建记录/ })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('有 1 个操作的结果尚未确认')).not.toBeInTheDocument();
|
||||
expect(screen.getByLabelText('名称')).toBeDisabled();
|
||||
expect(screen.getByRole('button', { name: '更换头像' })).toBeDisabled();
|
||||
expect(screen.getByRole('button', { name: '移除头像' })).toBeDisabled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '重试创建' }));
|
||||
await screen.findByTestId('cloud-agent-editor');
|
||||
expect(api.create.mock.calls[1][0]).toEqual(api.create.mock.calls[0][0]);
|
||||
await waitFor(() => expect(screen.queryByText('有 1 个操作的结果尚未确认')).not.toBeInTheDocument());
|
||||
expect(api.recovery).toHaveBeenCalledTimes(3);
|
||||
await waitFor(() => expect(api.recovery).toHaveBeenCalledTimes(3));
|
||||
expect(await api.recovery.mock.results.at(-1)!.value).toEqual({ recent: null, pending: [] });
|
||||
expect(api.uploadAvatar).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -747,11 +828,6 @@ it('keeps an uncertain creation identity after its reminder is removed and a ret
|
||||
let retainedPending: unknown[] = [];
|
||||
api.recovery.mockImplementation(async () => ({ recent: null, pending: retainedPending }));
|
||||
api.uploadAvatar.mockResolvedValue(avatar);
|
||||
api.resolvePending.mockImplementation(async (_id, discard) => {
|
||||
expect(discard).toBe(true);
|
||||
retainedPending = [];
|
||||
return { discarded: true };
|
||||
});
|
||||
api.create.mockImplementationOnce(async input => {
|
||||
retainedPending = [{ id: 'create:discarded', operation: 'createAgent', input, created_at: '2026-09-13T01:00:00Z' }];
|
||||
throw new AppError('NETWORK', '连接中断', undefined, { commandOutcome: 'unknown' });
|
||||
@@ -763,12 +839,18 @@ it('keeps an uncertain creation identity after its reminder is removed and a ret
|
||||
fireEvent.click(screen.getByRole('button', { name: '上传头像' }));
|
||||
await screen.findByRole('button', { name: '更换头像' });
|
||||
fireEvent.click(screen.getByRole('button', { name: '下一步', exact: true }));
|
||||
await screen.findByText('有 1 个操作的结果尚未确认');
|
||||
fireEvent.click(screen.getByRole('button', { name: '移除提醒', exact: true }));
|
||||
await waitFor(() => expect(screen.queryByText('有 1 个操作的结果尚未确认')).not.toBeInTheDocument());
|
||||
expect(api.resolvePending).toHaveBeenCalledWith('create:discarded', true);
|
||||
await screen.findByRole('alert');
|
||||
await waitFor(() => expect(api.recovery).toHaveBeenCalledTimes(2));
|
||||
expect(retainedPending).toHaveLength(1);
|
||||
// Clearing the journal independently must not change the still-mounted form's uncertainty.
|
||||
// The records entry is now confined to the list, and explicit discard is tested there.
|
||||
retainedPending = [];
|
||||
expect(screen.queryByRole('button', { name: /^创建记录/ })).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '重试创建', exact: true }));
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent('创建结果暂未确认,可重试确认结果。参数被拒绝');
|
||||
await waitFor(() => expect(api.recovery).toHaveBeenCalledTimes(3));
|
||||
expect(await api.recovery.mock.results.at(-1)!.value).toEqual({ recent: null, pending: [] });
|
||||
expect(api.resolvePending).not.toHaveBeenCalled();
|
||||
expect(screen.getByLabelText('名称')).toBeDisabled();
|
||||
expect(screen.getByLabelText('名称')).toHaveValue('原搭档');
|
||||
expect(screen.getByRole('button', { name: '更换头像' })).toBeDisabled();
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
import { act, cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
|
||||
import { CloudRecovery } from '@/pages/CloudAgents/CloudRecovery';
|
||||
import { AppError } from '@/lib/error-model';
|
||||
import type { CloudPendingOperation } from '../../shared/cloud-agents';
|
||||
|
||||
const api = vi.hoisted(() => ({ resolvePending: vi.fn() }));
|
||||
vi.mock('@/lib/cloud-agents-api', () => ({ cloudAgentsApi: api }));
|
||||
const first: CloudPendingOperation = { id: 'create:first', operation: 'createAgent', input: { operation_id: 'original-first', name: '故事搭档' }, created_at: '2026-10-13T03:54:49Z' };
|
||||
const second: CloudPendingOperation = { id: 'create:second', operation: 'createAgent', input: { operation_id: 'original-second', name: '阅读伙伴' }, created_at: '2026-10-13T03:55:18Z' };
|
||||
const key: CloudPendingOperation = { id: 'key:original', operation: 'createKey', input: { application_id: 'application', operation_id: 'original-key', name: '应用密钥' }, created_at: '2026-10-13T04:00:00Z' };
|
||||
const resolved = vi.fn();
|
||||
beforeEach(() => { vi.resetAllMocks(); });
|
||||
afterEach(cleanup);
|
||||
async function openRecords() {
|
||||
fireEvent.click(screen.getByRole('button', { name: /^(创建记录|待处理记录)/ }));
|
||||
return await screen.findByRole('dialog');
|
||||
}
|
||||
function deferred<T>() {
|
||||
let resolve!: (value: T) => void;
|
||||
const promise = new Promise<T>(yes => { resolve = yes; });
|
||||
return { promise, resolve };
|
||||
}
|
||||
|
||||
it('shows only a compact counted creation entry until explicitly opened, without retrying or discarding records', async () => {
|
||||
render(<CloudRecovery pending={[first, second]} onResolved={resolved} />);
|
||||
expect(screen.getByRole('button', { name: /^创建记录/ })).toHaveTextContent('2');
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('complementary', { name: '未确认的云端操作' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('article', { name: '创建「故事搭档」' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/有 2 个操作的结果尚未确认/)).not.toBeInTheDocument();
|
||||
const dialog = await openRecords();
|
||||
expect(within(dialog).getByRole('article', { name: '创建「故事搭档」' })).toBeVisible();
|
||||
expect(within(dialog).getByRole('article', { name: '创建「阅读伙伴」' })).toBeVisible();
|
||||
expect(within(dialog).getAllByRole('button', { name: '再试一次', exact: true })).toHaveLength(2);
|
||||
expect(within(dialog).getAllByRole('button', { name: '不再提醒', exact: true })).toHaveLength(2);
|
||||
expect(api.resolvePending).not.toHaveBeenCalled(); expect(resolved).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('uses a general records entry for mixed operations and keeps the original message visible only inside the dialog', async () => {
|
||||
const message = { ...second, id: 'message:original', operation: 'submit', input: { query: '我的上次问题' } };
|
||||
render(<CloudRecovery pending={[first, message]} onResolved={resolved} />);
|
||||
expect(screen.getByRole('button', { name: /^待处理记录/ })).toHaveTextContent('2');
|
||||
expect(screen.queryByText('我的上次问题')).not.toBeInTheDocument();
|
||||
const dialog = await openRecords();
|
||||
expect(within(dialog).getByText('我的上次问题')).toBeVisible();
|
||||
expect(api.resolvePending).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps failed records and retries the same record ID after closing and reopening the dialog', async () => {
|
||||
api.resolvePending.mockRejectedValueOnce(new Error('原操作仍未确认')).mockResolvedValueOnce({ result: { slug: 'original-agent' } });
|
||||
render(<CloudRecovery pending={[first]} onResolved={resolved} />);
|
||||
const dialog = await openRecords();
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: '再试一次' }));
|
||||
await screen.findByRole('alert');
|
||||
expect(screen.getByRole('alert')).toHaveTextContent('原操作仍未确认');
|
||||
expect(resolved).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '关闭记录' }));
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
const reopened = await openRecords();
|
||||
expect(within(reopened).getByRole('article', { name: '创建「故事搭档」' })).toBeVisible();
|
||||
fireEvent.click(within(reopened).getByRole('button', { name: '再试一次' }));
|
||||
await waitFor(() => expect(resolved).toHaveBeenCalledOnce());
|
||||
expect(api.resolvePending.mock.calls).toEqual([[first.id, false], [first.id, false]]);
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('removes only the explicitly chosen local reminder and never implicitly retries it', async () => {
|
||||
api.resolvePending.mockResolvedValue({ discarded: true });
|
||||
const view = render(<CloudRecovery pending={[first, second]} onResolved={resolved} />);
|
||||
const dialog = await openRecords();
|
||||
fireEvent.click(within(dialog).getAllByRole('button', { name: '不再提醒' })[1]);
|
||||
await waitFor(() => expect(resolved).toHaveBeenCalledOnce());
|
||||
expect(api.resolvePending.mock.calls).toEqual([[second.id, true]]);
|
||||
view.rerender(<CloudRecovery pending={[first]} onResolved={resolved} />);
|
||||
expect(screen.getByRole('article', { name: '创建「故事搭档」' })).toBeVisible();
|
||||
expect(screen.queryByRole('article', { name: '创建「阅读伙伴」' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('serializes double clicks and other actions and prevents dismissal until a request finishes', async () => {
|
||||
const response = deferred<{ result: { slug: string } }>();
|
||||
api.resolvePending.mockReturnValue(response.promise);
|
||||
render(<CloudRecovery pending={[first, second]} onResolved={resolved} />);
|
||||
const dialog = await openRecords();
|
||||
await act(async () => { await new Promise(resolve => setTimeout(resolve, 0)); });
|
||||
const retry = within(dialog).getAllByRole('button', { name: '再试一次' })[0];
|
||||
act(() => { fireEvent.click(retry); fireEvent.click(retry); });
|
||||
expect(api.resolvePending).toHaveBeenCalledTimes(1);
|
||||
expect(within(dialog).getByRole('button', { name: '关闭记录' })).toBeDisabled();
|
||||
for (const discard of within(dialog).getAllByRole('button', { name: '不再提醒' })) expect(discard).toBeDisabled();
|
||||
fireEvent.keyDown(dialog, { key: 'Escape' });
|
||||
fireEvent.pointerDown(document.body, { button: 0, pointerType: 'mouse' });
|
||||
expect(screen.getByRole('dialog')).toBeVisible();
|
||||
expect(resolved).not.toHaveBeenCalled();
|
||||
await act(async () => response.resolve({ result: { slug: 'original-agent' } }));
|
||||
expect(resolved).toHaveBeenCalledOnce();
|
||||
expect(within(dialog).getByRole('button', { name: '关闭记录' })).toBeEnabled();
|
||||
});
|
||||
|
||||
it('keeps a returned key available after modal close and reopen until the result is explicitly closed', async () => {
|
||||
const secret = 'fixture-key-for-unit-only';
|
||||
api.resolvePending.mockResolvedValue({ result: { secret, id: 'created-key' } });
|
||||
const view = render(<CloudRecovery pending={[key, first]} onResolved={resolved} />);
|
||||
const dialog = await openRecords();
|
||||
fireEvent.click(within(dialog).getAllByRole('button', { name: '再试一次' })[0]);
|
||||
await screen.findByText(new RegExp(secret));
|
||||
view.rerender(<CloudRecovery pending={[first]} onResolved={resolved} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: '关闭记录' }));
|
||||
expect(screen.queryByText(new RegExp(secret))).not.toBeInTheDocument();
|
||||
await openRecords();
|
||||
expect(screen.getByText(new RegExp(secret))).toBeVisible();
|
||||
expect(api.resolvePending).toHaveBeenCalledTimes(1);
|
||||
fireEvent.click(screen.getByRole('button', { name: '关闭结果' }));
|
||||
expect(screen.queryByText(new RegExp(secret))).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '关闭记录' }));
|
||||
await openRecords();
|
||||
expect(screen.queryByText(new RegExp(secret))).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps a result entry available when the final pending record was acknowledged', async () => {
|
||||
api.resolvePending.mockResolvedValue({ result: { secret: 'last-key-fixture' } });
|
||||
const view = render(<CloudRecovery pending={[key]} onResolved={resolved} />);
|
||||
await openRecords(); fireEvent.click(screen.getByRole('button', { name: '再试一次' }));
|
||||
await screen.findByText(/last-key-fixture/);
|
||||
view.rerender(<CloudRecovery pending={[]} onResolved={resolved} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: '关闭记录' }));
|
||||
await openRecords(); expect(screen.getByText(/last-key-fixture/)).toBeVisible();
|
||||
fireEvent.click(screen.getByRole('button', { name: '关闭结果' }));
|
||||
expect(screen.queryByText(/last-key-fixture/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('guides verification requiring fresh input without claiming that verification completed', async () => {
|
||||
const verification = { ...first, id: 'wechat:verification', operation: 'wechatBindVerification', input: { session_key: 'fixture-session' } };
|
||||
api.resolvePending.mockResolvedValue({ requires_input: true, operation: verification.operation });
|
||||
render(<CloudRecovery pending={[verification]} onResolved={resolved} />);
|
||||
await openRecords(); fireEvent.click(screen.getByRole('button', { name: '再试一次' }));
|
||||
expect(await screen.findByText(/还需要验证码/)).toBeVisible();
|
||||
expect(screen.queryByText('微信连接验证的结果已确认。')).not.toBeInTheDocument();
|
||||
expect(api.resolvePending).toHaveBeenCalledWith(verification.id, false);
|
||||
});
|
||||
|
||||
it('offers only explicit discard for retired operations and explains the retired result without auto actions', async () => {
|
||||
const retired = { ...first, id: 'retired:policy', operation: 'channelPolicy', input: {} };
|
||||
api.resolvePending.mockResolvedValue({ retired: true, discarded: true, operation: retired.operation });
|
||||
render(<CloudRecovery pending={[retired]} onResolved={resolved} />);
|
||||
expect(api.resolvePending).not.toHaveBeenCalled();
|
||||
await openRecords();
|
||||
expect(screen.queryByRole('button', { name: '再试一次' })).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '丢弃旧记录' }));
|
||||
expect(await screen.findByText(/旧版联系人权限操作已移除/)).toBeVisible();
|
||||
expect(api.resolvePending).toHaveBeenCalledWith(retired.id, false);
|
||||
});
|
||||
|
||||
it('renders no entry or dialog when there are no pending records or retained results', () => {
|
||||
render(<CloudRecovery pending={[]} onResolved={resolved} />);
|
||||
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
expect(api.resolvePending).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not let another action replace an unclosed key result, and unlocks actions after explicit result closure', async () => {
|
||||
const verification = { ...second, operation: 'wechatBindVerification' };
|
||||
api.resolvePending.mockImplementation(async id => id === key.id
|
||||
? { result: { secret: 'protected-key-fixture' } } : { requires_input: true, operation: verification.operation });
|
||||
const view = render(<CloudRecovery pending={[key, verification]} onResolved={resolved} />);
|
||||
await openRecords();
|
||||
fireEvent.click(within(screen.getByRole('article', { name: '创建密钥' })).getByRole('button', { name: '再试一次' }));
|
||||
await screen.findByText(/protected-key-fixture/);
|
||||
view.rerender(<CloudRecovery pending={[verification]} onResolved={resolved} />);
|
||||
const row = screen.getByRole('article', { name: '微信连接验证' });
|
||||
const retry = within(row).getByRole('button', { name: '再试一次' });
|
||||
const discard = within(row).getByRole('button', { name: '不再提醒' });
|
||||
expect(retry).toBeDisabled(); expect(discard).toBeDisabled();
|
||||
fireEvent.click(retry); fireEvent.click(discard);
|
||||
expect(api.resolvePending).toHaveBeenCalledTimes(1);
|
||||
expect(screen.getByText(/protected-key-fixture/)).toBeVisible();
|
||||
fireEvent.click(screen.getByRole('button', { name: '关闭结果' }));
|
||||
expect(retry).toBeEnabled();
|
||||
fireEvent.click(retry);
|
||||
await screen.findByText(/还需要验证码/);
|
||||
expect(api.resolvePending.mock.calls).toEqual([[key.id, false], [verification.id, false]]);
|
||||
});
|
||||
|
||||
it('refreshes pending records after a definite rejection while keeping the error inside the explicit dialog', async () => {
|
||||
api.resolvePending.mockRejectedValue(new AppError('CONFIG', '这条创建已被拒绝', undefined, { commandOutcome: 'definitive_failure' }));
|
||||
const view = render(<CloudRecovery pending={[first]} onResolved={resolved} />);
|
||||
await openRecords(); fireEvent.click(screen.getByRole('button', { name: '再试一次' }));
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent('这条创建已被拒绝');
|
||||
expect(resolved).toHaveBeenCalledOnce();
|
||||
expect(api.resolvePending).toHaveBeenCalledWith(first.id, false);
|
||||
view.rerender(<CloudRecovery pending={[]} onResolved={resolved} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: '关闭记录' }));
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
await openRecords();
|
||||
expect(screen.queryByRole('article')).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('alert')).toHaveTextContent('这条创建已被拒绝');
|
||||
});
|
||||
|
||||
it('keeps an explicitly unknown command outcome recoverable without refreshing it as a definite failure', async () => {
|
||||
api.resolvePending.mockRejectedValue(new AppError('NETWORK', '请求结果仍未知', undefined, { commandOutcome: 'unknown' }));
|
||||
render(<CloudRecovery pending={[first]} onResolved={resolved} />);
|
||||
await openRecords(); fireEvent.click(screen.getByRole('button', { name: '再试一次' }));
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent('请求结果仍未知');
|
||||
expect(resolved).not.toHaveBeenCalled();
|
||||
expect(screen.getByRole('article', { name: '创建「故事搭档」' })).toBeVisible();
|
||||
fireEvent.click(screen.getByRole('button', { name: '再试一次' }));
|
||||
await waitFor(() => expect(api.resolvePending).toHaveBeenCalledTimes(2));
|
||||
expect(api.resolvePending.mock.calls).toEqual([[first.id, false], [first.id, false]]);
|
||||
});
|
||||
Reference in new issue
Block a user