Files
makelore/tests/unit/cloud-agent-entry-navigation.test.tsx
T

137 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { act, cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { createMemoryRouter, RouterProvider } from 'react-router-dom';
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
import { ModuleSelection } from '@/pages/ModuleSelection';
import CloudAgents from '@/pages/CloudAgents';
import { EMPTY_CLOUD_CONFIGURATION, type CloudAgentDraft, type CloudRecent } from '../../shared/cloud-agents';
const api = vi.hoisted(() => ({ list: vi.fn(), create: vi.fn(), uploadAvatar: vi.fn(), get: vi.fn(), save: vi.fn(), call: vi.fn(), events: vi.fn(), recovery: vi.fn(), remember: vi.fn(), resolvePending: vi.fn() }));
const auth = vi.hoisted(() => ({
user: { userId: 'creator', username: 'creator' },
moduleAccess: { programming: true, design: true, robot: true, cloud_agents: true },
isAuthenticated: () => true,
}));
vi.mock('@/lib/cloud-agents-api', () => ({ cloudAgentsApi: api }));
vi.mock('@/stores/auth', () => ({ useAuthStore: (selector: (state: typeof auth) => unknown) => selector(auth) }));
vi.mock('@/hooks/use-current-user-profile', () => ({ useCurrentUserProfile: () => ({ userProfile: undefined, profileRequired: false, profileSyncError: null }) }));
vi.mock('@/components/profile/UserProfileDialog', () => ({ UserProfileDialog: () => null }));
vi.mock('@/pages/CloudAgents/DraftEditor', () => ({
DraftEditor: ({ initial, entryView, recent }: { initial: CloudAgentDraft; entryView?: string; recent: CloudRecent | null }) =>
<div data-testid="entry-editor" data-entry-view={entryView ?? 'restored'} data-thread={recent?.thread_id}>{initial.name}</div>,
}));
vi.mock('@/pages/CloudAgents/CloudChat', () => ({ CloudChat: ({ slug }: { slug: string }) => <div data-testid="entry-chat">{slug}</div> }));
vi.mock('@/pages/CloudAgents/ChannelAccounts', () => ({ ChannelAccounts: () => <div>微信渠道页面</div> }));
const draft: CloudAgentDraft = {
slug: 'ml-' + 'a'.repeat(32), name: '学习朋友', purpose: '一起探索新知识', system_prompt: '回答问题',
draft_revision: 4, updated_at: '2026-10-12T00:00:00Z', configuration: EMPTY_CLOUD_CONFIGURATION,
published_version: 2, enabled: true,
};
const recent: CloudRecent = { slug: draft.slug, thread_id: 'recent-thread', mode: 'published' };
const pending = { id: 'pending-message', operation: 'submit', input: { query: '上次的问题' }, created_at: '2026-10-12T00:00:00Z' };
function open(entry = '/module-select') {
const router = createMemoryRouter([
{ path: '/module-select', element: <ModuleSelection /> },
{ path: '/cloud-agents/*', element: <CloudAgents /> },
], { initialEntries: [entry] });
render(<RouterProvider router={router} />);
return router;
}
beforeEach(() => {
vi.resetAllMocks();
api.list.mockResolvedValue({ agents: [draft], next_cursor: null });
api.get.mockResolvedValue(draft);
api.recovery.mockResolvedValue({ recent, pending: [pending] });
api.remember.mockResolvedValue({ saved: true });
});
afterEach(cleanup);
it('opens the card list from the home Agents choice without changing the remembered workspace', async () => {
const router = open();
fireEvent.click(screen.getByTestId('ai-module-option-cloud_agents'));
expect(await screen.findByRole('article', { name: draft.name })).toBeVisible();
const records = await screen.findByRole('button', { name: '待处理记录(1)' });
expect(screen.queryByRole('dialog', { name: '待处理记录' })).not.toBeInTheDocument();
expect(screen.queryByText('上次的问题')).not.toBeInTheDocument();
fireEvent.click(records);
const dialog = await screen.findByRole('dialog', { name: '待处理记录' });
expect(within(dialog).getByRole('article', { name: '发送消息' })).toHaveTextContent('上次的问题');
expect(within(dialog).getByRole('button', { name: '再试一次' })).toBeEnabled();
fireEvent.click(within(dialog).getByRole('button', { name: '关闭记录' }));
await waitFor(() => expect(screen.queryByRole('dialog', { name: '待处理记录' })).not.toBeInTheDocument());
fireEvent.click(screen.getByRole('button', { name: '待处理记录(1)' }));
const reopened = await screen.findByRole('dialog', { name: '待处理记录' });
expect(within(reopened).getByRole('article', { name: '发送消息' })).toHaveTextContent('上次的问题');
expect(router.state.location.pathname + router.state.location.search).toBe('/cloud-agents?view=mine');
expect(screen.queryByTestId('entry-editor')).not.toBeInTheDocument();
expect(api.get).not.toHaveBeenCalled();
expect(api.remember).not.toHaveBeenCalled();
expect(api.resolvePending).not.toHaveBeenCalled();
});
it('opens an available Agent conversation only after its card is selected', async () => {
open();
fireEvent.click(screen.getByTestId('ai-module-option-cloud_agents'));
fireEvent.click(await screen.findByRole('button', { name: '和它聊聊', exact: true }));
expect(await screen.findByTestId('entry-editor')).toHaveAttribute('data-entry-view', 'chat');
expect(screen.getByTestId('entry-editor')).not.toHaveAttribute('data-thread');
expect(api.call).not.toHaveBeenCalled();
});
it('does not replace an explicitly opened draft when recovery arrives late', async () => {
let resolveRecovery!: (value: { recent: CloudRecent; pending: typeof pending[] }) => void;
api.recovery.mockReturnValue(new Promise(resolve => { resolveRecovery = resolve; }));
api.list.mockResolvedValue({ agents: [{ ...draft, published_version: null }], next_cursor: null });
open();
fireEvent.click(screen.getByTestId('ai-module-option-cloud_agents'));
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
expect(screen.getByTestId('entry-editor')).toHaveAttribute('data-entry-view', 'configuration');
await act(async () => resolveRecovery({ recent: { ...recent, slug: 'ml-' + 'b'.repeat(32) }, pending: [pending] }));
expect(screen.getByTestId('entry-editor')).toHaveTextContent(draft.name);
expect(screen.getByTestId('entry-editor')).toHaveAttribute('data-entry-view', 'configuration');
expect(api.get).not.toHaveBeenCalled();
expect(api.remember).not.toHaveBeenCalled();
});
it('preserves remembered conversation recovery for a direct workspace entry', async () => {
open('/cloud-agents');
expect(await screen.findByTestId('entry-editor')).toHaveAttribute('data-thread', 'recent-thread');
expect(api.get).toHaveBeenCalledWith(draft.slug);
expect(api.remember).not.toHaveBeenCalled();
});
it('returns from a remembered workspace to the explicit card list without erasing history', async () => {
const router = open('/cloud-agents');
await screen.findByTestId('entry-editor');
api.get.mockClear();
await act(async () => { await router.navigate('/cloud-agents?view=mine'); });
expect(await screen.findByRole('article', { name: draft.name })).toBeVisible();
await waitFor(() => expect(api.recovery).toHaveBeenCalledTimes(2));
expect(screen.queryByTestId('entry-editor')).not.toBeInTheDocument();
expect(api.get).not.toHaveBeenCalled();
expect(api.remember).not.toHaveBeenCalled();
});
it('preserves explicit shared Agent links instead of reopening the remembered owner draft', async () => {
const sharedSlug = 'ml-' + 'b'.repeat(32);
api.call.mockResolvedValue({ slug: sharedSlug, name: '分享朋友', purpose: '', available: true });
open(`/cloud-agents/shared/${sharedSlug}`);
expect(await screen.findByTestId('entry-chat')).toHaveTextContent(sharedSlug);
expect(api.call).toHaveBeenCalledWith('entry', { slug: sharedSlug });
expect(api.get).not.toHaveBeenCalled();
expect(api.remember).not.toHaveBeenCalled();
});
it('preserves explicit channel links', async () => {
open('/cloud-agents/channels');
expect(screen.getByText('微信渠道页面')).toBeVisible();
expect(api.recovery).not.toHaveBeenCalled();
expect(api.list).not.toHaveBeenCalled();
});