修正图标素材只生成透明图集

后端图标素材生成只持久化扣绿后的 spritesheet 图集。

响应兼容保留 iconImageSrcs 但图标生成固定返回空数组。

前端测试改为验证只回填图集且不生成独立图标层。

同步 OpenAPI、编辑器文档和项目记忆中的图标素材生成约定。
This commit is contained in:
2026-06-27 18:47:34 +08:00
parent 2c43e2accc
commit a6671e7da7
9 changed files with 141 additions and 198 deletions
@@ -70,6 +70,16 @@ vi.mock('./ImageCanvasUiAssetExtractionRasterModel', () => ({
renderUiDesignAssetExtractionMarkedImageMock,
}));
vi.mock('./ImageCanvasProjectCoverSnapshotModel', async () => {
const actual = await vi.importActual<
typeof import('./ImageCanvasProjectCoverSnapshotModel')
>('./ImageCanvasProjectCoverSnapshotModel');
return {
...actual,
createProjectCoverSnapshotBlob: vi.fn(async () => null),
};
});
describe('ImageCanvasEditorView generation integration', () => {
setupImageCanvasEditorViewTestLifecycle({
generateEditorImageMock,
@@ -1488,14 +1498,7 @@ describe('ImageCanvasEditorView generation integration', () => {
spritesheetImageSrc: 'data:image/png;base64,sheet-gpt-model',
spritesheetWidth: 1024,
spritesheetHeight: 1024,
iconImageSrcs: [
{
name: '返回按钮',
imageSrc: 'data:image/png;base64,back',
width: 128,
height: 128,
},
],
iconImageSrcs: [],
prompt: '图标 prompt',
actualPrompt: '图标 prompt',
model: 'gpt-image-2',
@@ -2241,7 +2244,7 @@ describe('ImageCanvasEditorView generation integration', () => {
expect(screen.getByAltText('画布图片:拼图素材')).toBeTruthy();
});
it('opens icon asset generation panel, only picks icon specs, and lays generated icons on canvas', async () => {
it('opens icon asset generation panel, only picks icon specs, and lays only the generated spritesheet on canvas', async () => {
loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({
projectId: 'editor-project-icons',
title: '图标素材画布',
@@ -2284,20 +2287,7 @@ describe('ImageCanvasEditorView generation integration', () => {
spritesheetImageSrc: 'data:image/png;base64,sheet',
spritesheetWidth: 512,
spritesheetHeight: 512,
iconImageSrcs: [
{
name: '返回按钮',
imageSrc: 'data:image/png;base64,back-icon',
width: 96,
height: 96,
},
{
name: '设置按钮',
imageSrc: 'data:image/png;base64,setting-icon',
width: 96,
height: 96,
},
],
iconImageSrcs: [],
prompt: '图标 prompt',
actualPrompt: '图标 prompt',
model: 'gemini-3.1-flash-image-preview',
@@ -2324,7 +2314,7 @@ describe('ImageCanvasEditorView generation integration', () => {
name: '素材描述',
}) as HTMLTextAreaElement
).value,
).toBe('返回按钮\n设置按钮\n下一关按钮\n提示按钮\n原图按钮\n冻结按钮');
).toBe('');
fireEvent.click(
within(iconPanel).getByRole('button', { name: '图标规范' }),
@@ -2371,34 +2361,37 @@ describe('ImageCanvasEditorView generation integration', () => {
);
fireEvent.click(within(iconPanel).getByRole('button', { name: '生成' }));
expect(generateEditorIconSpritesheetMock).toHaveBeenCalledWith(
expect.objectContaining({
referenceImageSrc: 'data:image/png;base64,icon-spec',
iconDescriptions: ['返回按钮', '设置按钮'],
model: 'gemini-3.1-flash-image-preview',
aspectRatio: '1:1',
imageSize: '1K',
projectId: 'editor-project-icons',
assetFolderId: 'project',
generationInputs: expect.objectContaining({
fields: expect.arrayContaining([
expect.objectContaining({
title: '素材描述',
value: '返回按钮\n设置按钮',
}),
]),
await waitFor(() => {
expect(generateEditorIconSpritesheetMock).toHaveBeenCalledWith(
expect.objectContaining({
referenceImageSrc: 'data:image/png;base64,icon-spec',
iconDescriptions: ['返回按钮', '设置按钮'],
model: 'gemini-3.1-flash-image-preview',
aspectRatio: '1:1',
imageSize: '1K',
projectId: 'editor-project-icons',
assetFolderId: 'project',
generationInputs: expect.objectContaining({
fields: expect.arrayContaining([
expect.objectContaining({
title: '素材描述',
value: '返回按钮\n设置按钮',
}),
]),
}),
}),
}),
);
);
});
await waitFor(() => {
expect(screen.getByAltText('画布图片:返回按钮')).toBeTruthy();
expect(screen.getByAltText('画布图片:设置按钮')).toBeTruthy();
expect(screen.getByAltText(/图层缩略图:图标素材图集/u)).toBeTruthy();
});
expect(screen.queryByAltText('画布图片:返回按钮')).toBeNull();
expect(screen.queryByAltText('画布图片:设置按钮')).toBeNull();
expect(screen.queryByLabelText('图标素材生成占位图')).toBeNull();
expect(screen.getAllByText('图标')).toHaveLength(2);
expect(screen.getByText('图集')).toBeTruthy();
fireEvent.click(
screen.getAllByRole('button', { name: '查看返回按钮图片信息' })[0]!,
screen.getByRole('button', { name: /查看图标素材图集.*图片信息/u }),
);
const iconInfoPanel = screen.getByRole('dialog', { name: '图片信息' });
expect(within(iconInfoPanel).queryByText('Prompt')).toBeNull();
@@ -2573,14 +2566,7 @@ describe('ImageCanvasEditorView generation integration', () => {
spritesheetImageSrc: 'data:image/png;base64,cmVuYW1lZC1zaGVldA==',
spritesheetWidth: 512,
spritesheetHeight: 512,
iconImageSrcs: [
{
name: '背包按钮',
imageSrc: 'data:image/png;base64,YmFncGFjay1pY29u',
width: 96,
height: 96,
},
],
iconImageSrcs: [],
prompt: '图标 prompt',
actualPrompt: '图标 prompt',
model: 'gemini-3.1-flash-image-preview',
@@ -2651,20 +2637,23 @@ describe('ImageCanvasEditorView generation integration', () => {
);
fireEvent.click(within(iconPanel).getByRole('button', { name: '生成' }));
expect(generateEditorIconSpritesheetMock).toHaveBeenCalledWith(
expect.objectContaining({
referenceImageSrc: 'data:image/png;base64,cmVuYW1lZC1pY29uLXNwZWM=',
iconDescriptions: ['背包按钮'],
model: 'gemini-3.1-flash-image-preview',
aspectRatio: '1:1',
imageSize: '1K',
projectId: 'editor-project-default',
assetFolderId: 'project',
}),
);
await waitFor(() => {
expect(screen.getByAltText('画布图片:背包按钮')).toBeTruthy();
expect(generateEditorIconSpritesheetMock).toHaveBeenCalledWith(
expect.objectContaining({
referenceImageSrc: 'data:image/png;base64,cmVuYW1lZC1pY29uLXNwZWM=',
iconDescriptions: ['背包按钮'],
model: 'gemini-3.1-flash-image-preview',
aspectRatio: '1:1',
imageSize: '1K',
projectId: 'editor-project-default',
assetFolderId: 'project',
}),
);
});
await waitFor(() => {
expect(screen.getByAltText(/图层缩略图:图标素材图集/u)).toBeTruthy();
});
expect(screen.queryByAltText('画布图片:背包按钮')).toBeNull();
});
it('exits character generation canvas picking with Escape', () => {
@@ -1537,10 +1537,7 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
model: 'gpt-image-2',
provider: 'VectorEngine',
taskId: 'task-icons',
iconImageSrcs: [
createIconResult('返回按钮', 'data:image/png;base64,back'),
createIconResult('设置按钮', 'data:image/png;base64,setting'),
],
iconImageSrcs: [],
});
render(
<SubmissionWorkflowHarness
@@ -1586,11 +1583,8 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
'layer-icon-spritesheet-1:图标素材图集 1:-:icon-spritesheet',
);
});
expect(screen.getByTestId('layers').textContent).toContain(
'layer-icon-2:返回按钮:-:icon',
);
expect(screen.getByTestId('layers').textContent).toContain(
'layer-icon-3:设置按钮:-:icon',
expect(screen.getByTestId('layers').textContent).not.toContain(
'layer-icon-2',
);
expect(screen.getByTestId('selected').textContent).toBe(
'layer-icon-spritesheet-1',
@@ -1616,9 +1610,7 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
model: 'gpt-image-2',
provider: 'VectorEngine',
taskId: 'task-icons',
iconImageSrcs: [
createIconResult('返回按钮', 'data:image/png;base64,back'),
],
iconImageSrcs: [],
});
render(
<SubmissionWorkflowHarness
@@ -780,14 +780,7 @@ describe('editorProjectClient', () => {
spritesheetImageSrc: 'data:image/png;base64,sheet',
spritesheetWidth: 512,
spritesheetHeight: 512,
iconImageSrcs: [
{
name: '返回按钮',
imageSrc: 'data:image/png;base64,back',
width: 96,
height: 96,
},
],
iconImageSrcs: [],
prompt: '图标素材 prompt',
actualPrompt: '图标素材 prompt',
model: 'gemini-3.1-flash-image-preview',