246 lines
9.2 KiB
TypeScript
246 lines
9.2 KiB
TypeScript
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<typeof import('@/lib/image-workspace')>();
|
||
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(
|
||
<MemoryRouter initialEntries={['/image-canvas']}>
|
||
<ImageCanvas />
|
||
</MemoryRouter>,
|
||
);
|
||
|
||
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(
|
||
<MemoryRouter initialEntries={['/image-canvas']}>
|
||
<ImageCanvas />
|
||
</MemoryRouter>,
|
||
);
|
||
|
||
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(
|
||
<MemoryRouter initialEntries={['/image-canvas']}>
|
||
<ImageCanvas />
|
||
</MemoryRouter>,
|
||
);
|
||
|
||
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(
|
||
<MemoryRouter initialEntries={['/image-canvas']}>
|
||
<ImageCanvas />
|
||
</MemoryRouter>,
|
||
);
|
||
|
||
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(
|
||
<MemoryRouter initialEntries={['/image-canvas']}>
|
||
<ImageCanvas />
|
||
</MemoryRouter>,
|
||
);
|
||
|
||
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('上传的参考图');
|
||
});
|
||
});
|