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 { 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 }) =>
{initial.name}
, })); vi.mock('@/pages/CloudAgents/CloudChat', () => ({ CloudChat: ({ slug }: { slug: string }) =>
{slug}
})); vi.mock('@/pages/CloudAgents/ChannelAccounts', () => ({ ChannelAccounts: () =>
微信渠道页面
})); 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: }, { path: '/cloud-agents/*', element: }, ], { initialEntries: [entry] }); render(); 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(); expect(await screen.findByRole('complementary', { 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(); });