merge: integrate remote main
This commit is contained in:
@@ -93,11 +93,12 @@ describe('youth AI Design Canvas page', () => {
|
||||
expect(screen.queryByText(/规格版本|编译器|生成策略|字段决策/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('presents unfinished AI output as progress instead of a chat reply', () => {
|
||||
prepareWorkspace();
|
||||
const unfinishedDraft = '收到,我们要收到,我们要制作一张社团活动海报。';
|
||||
it('shows one unfinished assistant reply incrementally and replaces it with the canonical turn', async () => {
|
||||
const { workspace } = prepareWorkspace();
|
||||
const firstChunk = '收到,我们要制作';
|
||||
const unfinishedDraft = `${firstChunk}一张社团活动海报。`;
|
||||
useImageWorkspaceStore.setState({
|
||||
assistantStreams: { 'operation-chat-1': unfinishedDraft },
|
||||
assistantStreams: { 'operation-chat-1': firstChunk },
|
||||
pendingOperations: {
|
||||
'operation-chat-1': {
|
||||
id: 'operation-chat-1',
|
||||
@@ -121,9 +122,69 @@ describe('youth AI Design Canvas page', () => {
|
||||
render(<ImageCanvas />);
|
||||
|
||||
const conversation = screen.getByTestId('image-workspace-conversation');
|
||||
expect(within(conversation).getByRole('status')).toHaveTextContent('AI 正在整理你的想法');
|
||||
const pendingMessage = within(conversation).getByTestId('pending-design-chat-operation-chat-1');
|
||||
expect(within(pendingMessage).getByText('做一张社团活动海报')).toBeInTheDocument();
|
||||
expect(within(pendingMessage).getByText('发送中')).toBeInTheDocument();
|
||||
expect(within(conversation).queryByRole('button', { name: '下一步:看看制作方案' }))
|
||||
.not.toBeInTheDocument();
|
||||
expect(within(conversation).queryByTestId('design-assistant-progress')).not.toBeInTheDocument();
|
||||
expect(within(conversation).queryByText('AI 正在整理你的想法')).not.toBeInTheDocument();
|
||||
expect(within(conversation).getByText('我已经整理了用途、受众和初步概念,请确认右侧建议。')).toBeInTheDocument();
|
||||
expect(within(conversation).queryByText(unfinishedDraft)).not.toBeInTheDocument();
|
||||
const streamingReply = within(conversation)
|
||||
.getByTestId('streaming-design-assistant-operation-chat-1');
|
||||
expect(streamingReply).toHaveTextContent(firstChunk);
|
||||
|
||||
act(() => {
|
||||
useImageWorkspaceStore.setState({
|
||||
assistantStreams: { 'operation-chat-1': unfinishedDraft },
|
||||
});
|
||||
});
|
||||
expect(streamingReply).toHaveTextContent(unfinishedDraft);
|
||||
|
||||
act(() => {
|
||||
useImageWorkspaceStore.setState({
|
||||
workspace: {
|
||||
...workspace,
|
||||
turns: [...workspace.turns, {
|
||||
turnId: 'turn-2',
|
||||
rawTurnSequence: 2,
|
||||
userMessage: '做一张社团活动海报',
|
||||
assistantMessage: unfinishedDraft,
|
||||
}],
|
||||
},
|
||||
pendingOperations: {},
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(within(conversation).queryByTestId('streaming-design-assistant-operation-chat-1'))
|
||||
.not.toBeInTheDocument();
|
||||
});
|
||||
expect(within(conversation).getAllByText(unfinishedDraft)).toHaveLength(1);
|
||||
expect(within(conversation).getByRole('button', { name: '下一步:看看制作方案' }))
|
||||
.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('opens the mobile quote confirmation from the ready conversation without starting a paid task', async () => {
|
||||
const { actions } = prepareWorkspace();
|
||||
const quotedWorkspace = designWorkspaceFixture({
|
||||
form: designFormFixture({ activeQuotes: [designQuoteFixture()] }),
|
||||
});
|
||||
actions.requestQuote.mockImplementationOnce(async () => {
|
||||
useImageWorkspaceStore.setState({ workspace: quotedWorkspace });
|
||||
return quotedWorkspace;
|
||||
});
|
||||
render(<ImageCanvas />);
|
||||
|
||||
const conversation = screen.getByTestId('image-workspace-conversation');
|
||||
fireEvent.click(within(conversation).getByRole('button', { name: '下一步:看看制作方案' }));
|
||||
|
||||
expect(actions.requestQuote).toHaveBeenCalledOnce();
|
||||
expect(actions.confirmGeneration).not.toHaveBeenCalled();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: '制作方案准备好了' })).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByTestId('design-offered-quote')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows a submitted message immediately and replaces it with the canonical turn', async () => {
|
||||
@@ -334,9 +395,13 @@ describe('youth AI Design Canvas page', () => {
|
||||
});
|
||||
render(<ImageCanvas />);
|
||||
|
||||
expect(within(screen.getByTestId('image-workspace-conversation'))
|
||||
.getByRole('button', { name: '正在准备方案' })).toBeDisabled();
|
||||
openMobileCreationCard();
|
||||
expect(screen.getByText('还在确认刚才的操作')).toBeInTheDocument();
|
||||
expect(screen.getByText(/不会重复制作或重复扣费/)).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '看看制作方案', exact: true })).toBeDisabled();
|
||||
expect(actions.requestQuote).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '查看原来的结果' }));
|
||||
expect(actions.retryOperation).toHaveBeenCalledWith('operation-1');
|
||||
});
|
||||
|
||||
@@ -523,6 +523,25 @@ describe('V2 Living Form store', () => {
|
||||
const source = await loadedStore();
|
||||
useImageWorkspaceStore.getState().setFieldDraft('intent.purpose', '本地草稿');
|
||||
useImageWorkspaceStore.setState({
|
||||
assistantStreams: { 'operation-chat-unknown': '这段回复还没确认' },
|
||||
pendingOperations: {
|
||||
'operation-chat-unknown': {
|
||||
id: 'operation-chat-unknown',
|
||||
label: '发送创作想法',
|
||||
command: {
|
||||
kind: 'apply_input',
|
||||
workspaceId: 'workspace-1',
|
||||
sessionId: 'session-1',
|
||||
expectedDirectionRevision: 4,
|
||||
clientOperationId: 'operation-chat-unknown',
|
||||
input: { kind: 'chat', message: '保留这条未知结果' },
|
||||
},
|
||||
status: 'unknown',
|
||||
error: '结果尚未确认',
|
||||
clearDraftPaths: [],
|
||||
clearChatDraft: true,
|
||||
},
|
||||
},
|
||||
quoteBlockers: [{
|
||||
code: 'missing_choice',
|
||||
message: '还需要选择作品形状',
|
||||
@@ -573,9 +592,12 @@ describe('V2 Living Form store', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
expect(useImageWorkspaceStore.getState().assistantStreams).toEqual({
|
||||
'operation-chat-unknown': '这段回复还没确认',
|
||||
'operation-chat-1': '正在整理',
|
||||
});
|
||||
expect(useImageWorkspaceStore.getState()).toMatchObject({
|
||||
lastEventId: 'session-1:8',
|
||||
assistantStreams: { 'operation-chat-1': '正在整理' },
|
||||
fieldDrafts: { 'intent.purpose': '本地草稿' },
|
||||
quoteBlockers: [],
|
||||
workspace: { form: { directionRevision: 5, specificationRevision: 4 } },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { cleanup, fireEvent, render, screen, within } from '@testing-library/react';
|
||||
import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { YouthCreationCard } from '@/pages/ImageCanvas/YouthCreationCard';
|
||||
import { DesignTaskPanel } from '@/pages/ImageCanvas/DesignProductionPanel';
|
||||
@@ -21,12 +21,14 @@ function renderCard(
|
||||
quoteBlockers?: DesignCompilationIssue[];
|
||||
onOpenFineTune?: () => void;
|
||||
onFocusComposer?: () => void;
|
||||
onQuoteOffered?: () => void;
|
||||
} = {},
|
||||
) {
|
||||
const requestQuote = vi.fn().mockResolvedValue(workspace);
|
||||
const resolveDecisionPrompt = vi.fn().mockResolvedValue(workspace);
|
||||
const onOpenFineTune = options.onOpenFineTune ?? vi.fn();
|
||||
const onFocusComposer = options.onFocusComposer ?? vi.fn();
|
||||
const onQuoteOffered = options.onQuoteOffered ?? vi.fn();
|
||||
useImageWorkspaceStore.setState({
|
||||
workspace,
|
||||
quoteBlockers: [],
|
||||
@@ -41,6 +43,7 @@ function renderCard(
|
||||
generationAvailable={options.generationAvailable ?? true}
|
||||
onOpenFineTune={onOpenFineTune}
|
||||
onFocusComposer={onFocusComposer}
|
||||
onQuoteOffered={onQuoteOffered}
|
||||
/>,
|
||||
);
|
||||
return {
|
||||
@@ -49,6 +52,7 @@ function renderCard(
|
||||
resolveDecisionPrompt,
|
||||
onOpenFineTune,
|
||||
onFocusComposer,
|
||||
onQuoteOffered,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -97,8 +101,8 @@ describe('YouthCreationCard', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('shows a plain creation summary and offers a youth-facing ready action', () => {
|
||||
const { requestQuote } = renderCard();
|
||||
it('shows a plain creation summary and reveals the offered quote after requesting it', async () => {
|
||||
const { onQuoteOffered, requestQuote } = renderCard();
|
||||
|
||||
expect(screen.getByRole('heading', { name: 'AI 听懂的想法' })).toBeInTheDocument();
|
||||
expect(screen.getByText('会跟着聊天自动更新,不需要逐项填写。')).toBeInTheDocument();
|
||||
@@ -112,6 +116,7 @@ describe('YouthCreationCard', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: '看看制作方案' }));
|
||||
|
||||
expect(requestQuote).toHaveBeenCalledOnce();
|
||||
await waitFor(() => expect(onQuoteOffered).toHaveBeenCalledOnce());
|
||||
});
|
||||
|
||||
it('does not surface legacy decision prompts as a form', () => {
|
||||
|
||||
Reference in New Issue
Block a user