合并主分支
解决合并冲突
This commit is contained in:
@@ -44,6 +44,7 @@ const renderUiDesignAssetExtractionMarkedImageMock = vi.hoisted(() => vi.fn());
|
||||
const generateEditorCharacterAnimationMock = vi.hoisted(() => vi.fn());
|
||||
const generateEditorIconSpritesheetMock = vi.hoisted(() => vi.fn());
|
||||
const generateEditorImageMock = vi.hoisted(() => vi.fn());
|
||||
const generateEditorSceneMock = vi.hoisted(() => vi.fn());
|
||||
const generateEditorVideoMock = vi.hoisted(() => vi.fn());
|
||||
const generateEditorSoundEffectMock = vi.hoisted(() => vi.fn());
|
||||
const generateEditorBackgroundMusicMock = vi.hoisted(() => vi.fn());
|
||||
@@ -67,6 +68,7 @@ vi.mock('../../services/image-editor/editorProjectClient', async () => {
|
||||
generateEditorCharacterAnimation: generateEditorCharacterAnimationMock,
|
||||
generateEditorIconSpritesheet: generateEditorIconSpritesheetMock,
|
||||
generateEditorImage: generateEditorImageMock,
|
||||
generateEditorScene: generateEditorSceneMock,
|
||||
generateEditorVideo: generateEditorVideoMock,
|
||||
generateEditorSoundEffect: generateEditorSoundEffectMock,
|
||||
generateEditorBackgroundMusic: generateEditorBackgroundMusicMock,
|
||||
@@ -575,6 +577,7 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
generateEditorCharacterAnimationMock.mockReset();
|
||||
generateEditorIconSpritesheetMock.mockReset();
|
||||
generateEditorImageMock.mockReset();
|
||||
generateEditorSceneMock.mockReset();
|
||||
generateEditorVideoMock.mockReset();
|
||||
generateEditorSoundEffectMock.mockReset();
|
||||
generateEditorBackgroundMusicMock.mockReset();
|
||||
@@ -1723,6 +1726,263 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('submits scene intent through the dedicated scene endpoint', async () => {
|
||||
generateEditorSceneMock.mockResolvedValueOnce(
|
||||
createGenerated({
|
||||
width: 1024,
|
||||
height: 576,
|
||||
prompt: '雨夜中的欧洲小镇街道',
|
||||
}),
|
||||
);
|
||||
render(
|
||||
<SubmissionWorkflowHarness
|
||||
initialDialog={{
|
||||
id: 'dialog-scene',
|
||||
mode: 'scene',
|
||||
prompt: '雨夜中的欧洲小镇街道',
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
sceneStylePreset: 'anime',
|
||||
sceneCustomStyle: '',
|
||||
generationReferences: [],
|
||||
imageModel: 'gemini-3.1-flash-image-preview',
|
||||
aspectRatio: '16:9',
|
||||
imageSize: '1K',
|
||||
placeholder: {
|
||||
x: 100,
|
||||
y: 100,
|
||||
width: 1024,
|
||||
height: 576,
|
||||
originalWidth: 1024,
|
||||
originalHeight: 576,
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '设置初始对话' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '提交当前生成' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(generateEditorSceneMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sceneContent: '雨夜中的欧洲小镇街道',
|
||||
stylePreset: 'anime',
|
||||
model: 'gemini-3.1-flash-image-preview',
|
||||
aspectRatio: '16:9',
|
||||
imageSize: '1K',
|
||||
assetLabel: '游戏场景 1',
|
||||
generationInputs: {
|
||||
fields: [
|
||||
{ title: '画面内容', value: '雨夜中的欧洲小镇街道' },
|
||||
{ title: '视觉风格', value: '日系动画' },
|
||||
],
|
||||
references: [],
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(generateEditorImageMock).not.toHaveBeenCalled();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('layers').textContent).toContain(
|
||||
'layer-generated-1:游戏场景 1:-:scene',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('forwards inline scene generation warnings before adding the result', async () => {
|
||||
generateEditorSceneMock.mockResolvedValueOnce(
|
||||
createGenerated({
|
||||
width: 1024,
|
||||
height: 576,
|
||||
warning: {
|
||||
code: 'delivery-size-normalization-failed',
|
||||
reason: '尺寸恢复失败,已保留生成服务返回的实际尺寸。',
|
||||
},
|
||||
}),
|
||||
);
|
||||
render(
|
||||
<SubmissionWorkflowHarness
|
||||
initialDialog={{
|
||||
id: 'dialog-scene-warning',
|
||||
mode: 'scene',
|
||||
prompt: '雨夜海边车站',
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
sceneStylePreset: 'anime',
|
||||
sceneCustomStyle: '',
|
||||
generationReferences: [],
|
||||
imageModel: 'gemini-3.1-flash-image-preview',
|
||||
aspectRatio: '16:9',
|
||||
imageSize: '1K',
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '设置初始对话' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '提交当前生成' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('generation-warning').textContent).toBe(
|
||||
'尺寸恢复失败,已保留生成服务返回的实际尺寸。',
|
||||
);
|
||||
});
|
||||
expect(screen.getByTestId('layers').textContent).toContain(
|
||||
'layer-generated-1:游戏场景 1:-:scene',
|
||||
);
|
||||
});
|
||||
|
||||
it('reloads the authoritative scene project when the first queued snapshot is unresolved', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const applyProjectSnapshot = vi.fn();
|
||||
const unresolvedProject = {
|
||||
projectId: 'editor-project-1',
|
||||
title: '场景队列项目',
|
||||
viewport: { x: 0, y: 0, scale: 1 },
|
||||
layers: [
|
||||
{
|
||||
layerId: 'generation-dialog-scene-queued',
|
||||
resourceId: 'generation-dialog-scene-queued',
|
||||
itemType: 'generation-dialog',
|
||||
dialog: {
|
||||
id: 'dialog-scene-queued',
|
||||
mode: 'scene',
|
||||
status: 'generating',
|
||||
},
|
||||
},
|
||||
],
|
||||
resources: [],
|
||||
updatedAt: '2026-08-06T08:00:00.000Z',
|
||||
};
|
||||
const completedProject = {
|
||||
...unresolvedProject,
|
||||
layers: [
|
||||
{
|
||||
layerId: 'layer-scene-result',
|
||||
resourceId: 'resource-scene-result',
|
||||
title: '游戏场景 1',
|
||||
assetKind: 'scene',
|
||||
},
|
||||
{
|
||||
layerId: 'generation-dialog-scene-queued',
|
||||
resourceId: 'generation-dialog-scene-queued',
|
||||
itemType: 'generation-dialog',
|
||||
dialog: {
|
||||
id: 'dialog-scene-queued',
|
||||
mode: 'scene',
|
||||
status: 'idle',
|
||||
generatedLayerId: 'layer-scene-result',
|
||||
},
|
||||
},
|
||||
],
|
||||
updatedAt: '2026-08-06T08:00:01.000Z',
|
||||
};
|
||||
generateEditorSceneMock.mockResolvedValueOnce({
|
||||
...createGenerated({ width: 1024, height: 576 }),
|
||||
queueState: createQueueState(),
|
||||
});
|
||||
loadEditorProjectMock
|
||||
.mockResolvedValueOnce(unresolvedProject)
|
||||
.mockResolvedValueOnce(completedProject);
|
||||
render(
|
||||
<SubmissionWorkflowHarness
|
||||
projectId="editor-project-1"
|
||||
applyProjectSnapshot={applyProjectSnapshot}
|
||||
initialDialog={{
|
||||
id: 'dialog-scene-queued',
|
||||
mode: 'scene',
|
||||
prompt: '雨夜海边车站',
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
sceneStylePreset: 'anime',
|
||||
sceneCustomStyle: '',
|
||||
generationReferences: [],
|
||||
imageModel: 'gemini-3.1-flash-image-preview',
|
||||
aspectRatio: '16:9',
|
||||
imageSize: '1K',
|
||||
placeholder: {
|
||||
x: 100,
|
||||
y: 100,
|
||||
width: 1024,
|
||||
height: 576,
|
||||
originalWidth: 1024,
|
||||
originalHeight: 576,
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '设置初始对话' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '提交当前生成' }));
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(loadEditorProjectMock).toHaveBeenCalledTimes(1);
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(650);
|
||||
});
|
||||
expect(loadEditorProjectMock).toHaveBeenCalledTimes(2);
|
||||
expect(applyProjectSnapshot).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
unresolvedProject,
|
||||
);
|
||||
expect(applyProjectSnapshot).toHaveBeenNthCalledWith(2, completedProject);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not add a local scene layer when the backend omits its project snapshot', async () => {
|
||||
generateEditorSceneMock.mockResolvedValueOnce(
|
||||
createGenerated({
|
||||
width: 1024,
|
||||
height: 576,
|
||||
project: null,
|
||||
}),
|
||||
);
|
||||
render(
|
||||
<SubmissionWorkflowHarness
|
||||
projectId="editor-project-1"
|
||||
initialDialog={{
|
||||
id: 'dialog-scene-authority',
|
||||
mode: 'scene',
|
||||
prompt: '雨夜海边车站',
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
sceneStylePreset: 'anime',
|
||||
sceneCustomStyle: '',
|
||||
generationReferences: [],
|
||||
imageModel: 'gemini-3.1-flash-image-preview',
|
||||
aspectRatio: '16:9',
|
||||
imageSize: '1K',
|
||||
placeholder: {
|
||||
x: 100,
|
||||
y: 100,
|
||||
width: 1024,
|
||||
height: 576,
|
||||
originalWidth: 1024,
|
||||
originalHeight: 576,
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '设置初始对话' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '提交当前生成' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('dialog').textContent).toBe(
|
||||
'scene:idle:open:-:placeholder:-',
|
||||
);
|
||||
});
|
||||
expect(screen.getByTestId('layers').textContent).not.toContain(
|
||||
'layer-generated-1',
|
||||
);
|
||||
});
|
||||
|
||||
it('uses the latest moved canvas placeholder for local-only generation fallback', async () => {
|
||||
const callOrder: string[] = [];
|
||||
const captureCanvasHistory = vi.fn((action: CanvasHistoryAction) => {
|
||||
|
||||
Reference in New Issue
Block a user