Files
makelore/tests/unit/image-canvas-page.test.tsx

190 lines
7.3 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 { fireEvent, render, screen, within } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { ImageCanvas } from '@/pages/ImageCanvas';
import { useAuthStore } from '@/stores/auth';
import { useImagePromptMuseumStore } from '@/stores/image-prompt-museum';
import { useImageWorkspaceStore } from '@/stores/image-workspace';
import {
designBootstrapFixture,
designFormFixture,
designQuoteFixture,
designWorkspaceFixture,
} from '../fixtures/design-workspace-v2';
vi.mock('@/lib/image-workspace', async (importOriginal) => {
const original = await importOriginal<typeof import('@/lib/image-workspace')>();
return {
...original,
resolveImageWorkspaceAssetUrl: vi.fn().mockResolvedValue('http://127.0.0.1/asset'),
saveImageWorkspaceAsset: vi.fn().mockResolvedValue({ status: 'saved' }),
uploadImageWorkspaceAsset: vi.fn(),
};
});
function prepareWorkspace(overrides = {}) {
const workspace = designWorkspaceFixture(overrides);
const actions = {
load: vi.fn(),
connectEvents: vi.fn(),
disconnectEvents: vi.fn(),
refreshWorkspace: vi.fn().mockResolvedValue(workspace),
applyFieldOperations: vi.fn().mockResolvedValue(workspace),
sendChat: vi.fn().mockResolvedValue(workspace),
resolveDecisionPrompt: vi.fn().mockResolvedValue(workspace),
requestQuote: vi.fn().mockResolvedValue(workspace),
confirmGeneration: vi.fn().mockResolvedValue(workspace),
retryOperation: vi.fn().mockResolvedValue(workspace),
};
useImageWorkspaceStore.setState({
status: 'ready',
bootstrap: designBootstrapFixture(workspace.workspace),
activeWorkspaceId: workspace.workspace.workspaceId,
workspace,
eventState: 'connected',
lastEventId: null,
pendingOperations: {},
fieldDrafts: {},
chatDraft: '',
assistantStreams: {},
quoteBlockers: [],
error: null,
...actions,
});
return { workspace, actions };
}
function openMobileCreationCard() {
fireEvent.click(screen.getByRole('button', { name: /^我的创作/ }));
}
describe('youth AI Design Canvas page', () => {
beforeEach(() => {
vi.clearAllMocks();
useAuthStore.setState({
initialized: true,
accessToken: 'token',
expiresAt: Date.now() + 60_000,
});
useImagePromptMuseumStore.setState({ pendingPrompt: null });
useImageWorkspaceStore.getState().reset();
});
it('starts with conversation and a concise Creation Card instead of a professional field matrix', () => {
prepareWorkspace();
render(<ImageCanvas />);
expect(screen.getByTestId('image-workspace-conversation')).toBeInTheDocument();
expect(screen.getByRole('heading', { name: '一起做作品' })).toBeInTheDocument();
openMobileCreationCard();
const card = screen.getByTestId('youth-creation-card');
expect(card).toBeInTheDocument();
expect(within(card).getByRole('heading', { name: '我的创作' })).toBeInTheDocument();
expect(screen.getByText('做一张便携咖啡机的小红书主视觉')).toBeInTheDocument();
expect(screen.queryByText('Living Form')).not.toBeInTheDocument();
expect(screen.queryByText('目标与用途')).not.toBeInTheDocument();
expect(screen.queryByText(/规格版本|编译器|生成策略|字段决策/)).not.toBeInTheDocument();
});
it('sends high-impact fine adjustments through the existing typed field operations', () => {
const { actions } = prepareWorkspace();
render(<ImageCanvas />);
openMobileCreationCard();
fireEvent.click(screen.getByRole('button', { name: '精细调整' }));
fireEvent.click(screen.getByRole('button', { name: '电脑横屏' }));
expect(actions.applyFieldOperations).toHaveBeenCalledWith(
[
{ kind: 'set', path: 'output.aspect_ratio', value: '16:9' },
{ kind: 'set', path: 'output.orientation', value: 'landscape' },
],
[],
);
});
it('shows a plain-language production confirmation and confirms only the Quote identity', () => {
const quote = designQuoteFixture();
quote.warnings = [{
code: 'reference_observation_fallback',
message: 'Reference observations were not reviewed by the frozen compiler.',
severity: 'warning',
path: 'references',
}];
const workspace = designWorkspaceFixture({
form: designFormFixture({ activeQuotes: [quote] }),
});
const { actions } = prepareWorkspace(workspace);
render(<ImageCanvas />);
openMobileCreationCard();
expect(screen.getByRole('heading', { name: '制作方案准备好了' })).toBeInTheDocument();
expect(screen.getByText('12')).toBeInTheDocument();
expect(screen.getByText('有张参考图还没检查完成AI 会谨慎使用它。')).toBeInTheDocument();
expect(screen.queryByText(/frozen compiler/i)).not.toBeInTheDocument();
expect(screen.queryByDisplayValue(/prompt/i)).not.toBeInTheDocument();
expect(screen.queryByText(/规格版本|报价已冻结|生成方式|不可变/)).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '花 12 设计点开始制作' }));
expect(actions.confirmGeneration).toHaveBeenCalledWith('quote-1');
});
it('prefills a museum Prompt without automatically submitting it', () => {
const { actions } = prepareWorkspace();
useImagePromptMuseumStore.setState({
pendingPrompt: { promptId: 'prompt-1', title: '灵感', prompt: '做一张冷色调产品海报' },
});
render(<ImageCanvas />);
expect(screen.getByDisplayValue('做一张冷色调产品海报')).toBeInTheDocument();
expect(actions.sendChat).not.toHaveBeenCalled();
});
it('explains an unknown accepted result without suggesting a second production', () => {
const { actions } = prepareWorkspace();
useImageWorkspaceStore.setState({
pendingOperations: {
'operation-1': {
id: 'operation-1',
label: '准备制作方案',
command: {
kind: 'request_quote',
workspaceId: 'workspace-1',
sessionId: 'session-1',
expectedDirectionRevision: 4,
specificationRevision: 3,
clientOperationId: 'operation-1',
},
status: 'unknown',
error: '网络已断开',
clearDraftPaths: [],
clearChatDraft: false,
},
},
});
render(<ImageCanvas />);
openMobileCreationCard();
expect(screen.getByText('还在确认刚才的操作')).toBeInTheDocument();
expect(screen.getByText(/不会重复制作或重复扣费/)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '查看原来的结果' }));
expect(actions.retryOperation).toHaveBeenCalledWith('operation-1');
});
it('opens project creation when the account has no Design Workspace', () => {
useImageWorkspaceStore.setState({
status: 'ready',
bootstrap: { ...designBootstrapFixture(), workspaces: [] },
activeWorkspaceId: null,
workspace: null,
error: null,
load: vi.fn(),
});
const listener = vi.fn();
window.addEventListener('niancode:image-workspace:create-project', listener);
render(<ImageCanvas />);
fireEvent.click(screen.getByRole('button', { name: '开始第一个创作' }));
expect(listener).toHaveBeenCalledOnce();
window.removeEventListener('niancode:image-workspace:create-project', listener);
});
});