import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { ImageWorkspaceApiError } from '@/lib/image-workspace'; import { ImageCanvas } from '@/pages/ImageCanvas'; import { useAuthStore } from '@/stores/auth'; import { useImageWorkspaceStore } from '@/stores/image-workspace'; import type { ImageWorkspaceSnapshot } from '../../shared/image-workspace'; const fetchImageWorkspaceMock = vi.hoisted(() => vi.fn()); const addImageWorkspaceAgentMock = vi.hoisted(() => vi.fn()); const sendImageWorkspaceMessageMock = vi.hoisted(() => vi.fn()); const uploadImageWorkspaceReferenceMock = vi.hoisted(() => vi.fn()); vi.mock('@/lib/image-workspace', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, fetchImageWorkspace: (...args: unknown[]) => fetchImageWorkspaceMock(...args), addImageWorkspaceAgent: (...args: unknown[]) => addImageWorkspaceAgentMock(...args), sendImageWorkspaceMessage: (...args: unknown[]) => sendImageWorkspaceMessageMock(...args), uploadImageWorkspaceReference: (...args: unknown[]) => uploadImageWorkspaceReferenceMock(...args), }; }); function workspaceFixture(): ImageWorkspaceSnapshot { return { activeProjectId: 'project-cloud', capabilities: { modes: [{ id: 'generate', label: '图片生成' }], models: [{ id: 'cloud-model', label: '云端模型 A' }], aspectRatios: [ { id: 'square', label: '1:1' }, { id: 'wide', label: '16:9' }, ], resolutions: [{ id: '2k', label: '2K' }], outputCounts: [{ id: 'four', label: '4 张' }], defaultModeId: 'generate', defaultModelId: 'cloud-model', defaultAspectRatioId: 'square', defaultResolutionId: '2k', defaultOutputCountId: 'four', maxReferenceImages: 3, referenceUpload: { enabled: true, acceptedMimeTypes: ['image/png', 'image/jpeg'], maxBytes: 5 * 1024 * 1024, }, }, projects: [{ id: 'project-cloud', name: '云端角色设定', activeAgentId: 'agent-default', updatedAt: '2026-07-28T10:00:00Z', agents: [{ id: 'agent-default', name: '视觉创作 Agent' }], messages: [{ id: 'message-result', role: 'assistant', agentId: 'agent-default', text: '已经为你生成一组角色图。', status: 'succeeded', createdAt: '2026-07-28T10:00:00Z', images: [{ id: 'image-cloud-1', url: 'https://example.com/cloud-result.png', thumbnailUrl: 'https://example.com/cloud-result-thumb.png', alt: '云端角色结果', }], }], }], }; } function resetAuthStore() { useAuthStore.setState({ initialized: true, loading: false, error: null, authBase: '', clientId: 'app', accessToken: 'access-token', refreshToken: null, tokenType: 'Bearer', expiresAt: Date.now() + 60_000, user: { username: 'creator', userId: 'user_1', tenantId: null, deptId: null, authorities: ['ROLE_USER'], }, }); } describe('ImageCanvas cloud conversation workspace', () => { beforeEach(() => { vi.clearAllMocks(); window.electron.imageWorkspaceLocalDevelopment = false; useImageWorkspaceStore.getState().reset(); resetAuthStore(); const workspace = workspaceFixture(); fetchImageWorkspaceMock.mockResolvedValue(workspace); addImageWorkspaceAgentMock.mockResolvedValue(workspace); sendImageWorkspaceMessageMock.mockResolvedValue(workspace); uploadImageWorkspaceReferenceMock.mockResolvedValue({ workspace, reference: { id: 'uploaded-reference', url: 'https://example.com/uploaded.png', alt: '上传的参考图', }, }); }); it('shows an honest unavailable state instead of local or fabricated projects', async () => { fetchImageWorkspaceMock.mockRejectedValueOnce(new ImageWorkspaceApiError( 501, 'IMAGE_WORKSPACE_UNAVAILABLE', '创作空间暂不可用', )); render( , ); expect(await screen.findByTestId('image-workspace-unavailable')).toHaveTextContent('创作空间暂不可用'); expect(screen.getByText(/暂时无法连接创作空间/)).toBeInTheDocument(); expect(screen.queryByText('制作中心')).not.toBeInTheDocument(); expect(screen.queryByText('任务模块')).not.toBeInTheDocument(); }); it('renders cloud projects, conversations, agents, and server-provided generation controls', async () => { render( , ); expect(await screen.findByTestId('image-workspace-page')).toHaveTextContent('云端角色设定'); expect(screen.getByText('当前 Agent:视觉创作 Agent')).toBeInTheDocument(); expect(screen.getByTestId('image-workspace-conversation')).toHaveTextContent('已经为你生成一组角色图'); expect(screen.getByRole('img', { name: '云端角色结果' })).toHaveAttribute( 'src', 'https://example.com/cloud-result-thumb.png', ); expect(screen.getByLabelText('创作模式')).toHaveValue('generate'); expect(screen.getByLabelText('模型')).toHaveValue('cloud-model'); expect(screen.getByLabelText('画幅')).toHaveValue('square'); expect(screen.getByLabelText('分辨率')).toHaveValue('2k'); expect(screen.getByLabelText('生成数量')).toHaveValue('four'); expect(screen.queryByText('Image2')).not.toBeInTheDocument(); }); it('renders the anonymous development adapter with the same production copy', async () => { useAuthStore.setState({ accessToken: null, refreshToken: null, expiresAt: null, }); window.electron.imageWorkspaceLocalDevelopment = true; fetchImageWorkspaceMock.mockResolvedValueOnce({ ...workspaceFixture(), activeProjectId: null, projects: [], }); render( , ); expect(await screen.findByText('创建第一个项目')).toBeInTheDocument(); expect(screen.getByRole('button', { name: '新建项目' })).toBeInTheDocument(); expect(fetchImageWorkspaceMock).toHaveBeenCalledWith(null); expect(screen.queryByText('本地开发')).not.toBeInTheDocument(); expect(screen.queryByText('创建第一个本地项目')).not.toBeInTheDocument(); expect(screen.queryByText('创建第一个云端项目')).not.toBeInTheDocument(); }); it('adds an output only after an explicit action and clears references after a successful turn', async () => { render( , ); await screen.findByTestId('image-workspace-page'); fireEvent.click(screen.getByRole('button', { name: '继续编辑' })); expect(screen.getByTestId('image-workspace-selected-references')).toHaveTextContent('云端角色结果'); fireEvent.change(screen.getByLabelText('创作描述'), { target: { value: '把角色换成夜景光线' } }); fireEvent.change(screen.getByLabelText('画幅'), { target: { value: 'wide' } }); fireEvent.click(screen.getByRole('button', { name: '生成图片' })); await waitFor(() => expect(sendImageWorkspaceMessageMock).toHaveBeenCalledWith('access-token', { projectId: 'project-cloud', agentId: 'agent-default', prompt: '把角色换成夜景光线', referenceImageIds: ['image-cloud-1'], settings: { modeId: 'generate', modelId: 'cloud-model', aspectRatioId: 'wide', resolutionId: '2k', outputCountId: 'four', }, })); await waitFor(() => expect(screen.queryByTestId('image-workspace-selected-references')).not.toBeInTheDocument()); fireEvent.change(screen.getByLabelText('创作描述'), { target: { value: '另起一张纯文字创作' } }); fireEvent.click(screen.getByRole('button', { name: '生成图片' })); await waitFor(() => expect(sendImageWorkspaceMessageMock).toHaveBeenLastCalledWith( 'access-token', expect.objectContaining({ prompt: '另起一张纯文字创作', referenceImageIds: [], }), )); }); it('uploads references through the cloud boundary and selects the returned cloud image', async () => { render( , ); await screen.findByTestId('image-workspace-page'); const file = new File(['image-bytes'], 'reference.png', { type: 'image/png' }); fireEvent.change(screen.getByLabelText('选择参考图'), { target: { files: [file] } }); await waitFor(() => expect(uploadImageWorkspaceReferenceMock).toHaveBeenCalledWith( 'access-token', expect.objectContaining({ projectId: 'project-cloud', fileName: 'reference.png', mimeType: 'image/png', }), )); expect(await screen.findByTestId('image-workspace-selected-references')).toHaveTextContent('上传的参考图'); }); });