diff --git a/src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx b/src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx index 918925b4f..27e840204 100644 --- a/src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx +++ b/src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx @@ -20,6 +20,7 @@ import { dispatchPointerEvent, ImageCanvasEditorView, setupImageCanvasEditorViewTestLifecycle, + withEditorProjectCanvasRevision, } from './ImageCanvasEditorView.test-utils'; function render(ui: Parameters[0]) { @@ -870,7 +871,7 @@ describe('ImageCanvasEditorView asset library integration', () => { it('saves the canvas layout after deleting an asset linked by source resource id', async () => { const user = userEvent.setup(); - loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ + loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce(withEditorProjectCanvasRevision({ projectId: 'editor-project-default', title: '生成资源项目', viewport: { x: 0, y: 0, scale: 1 }, @@ -900,7 +901,7 @@ describe('ImageCanvasEditorView asset library integration', () => { }, ], updatedAt: '2026-06-12T00:00:00.000Z', - }); + })); loadEditorAssetLibraryMock.mockResolvedValueOnce({ folders: [ { @@ -971,14 +972,14 @@ describe('ImageCanvasEditorView asset library integration', () => { height: 240, sourceType: 'uploaded', }); - loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ + loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce(withEditorProjectCanvasRevision({ projectId: 'editor-project-default', title: '空画布项目', viewport: { x: 0, y: 0, scale: 1 }, layers: [], resources: [], updatedAt: '2026-06-12T00:00:00.000Z', - }); + })); loadEditorAssetLibraryMock.mockResolvedValueOnce({ folders: [ { diff --git a/src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx b/src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx index 05a945f78..a4458b743 100644 --- a/src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx +++ b/src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx @@ -410,6 +410,7 @@ describe('ImageCanvasEditorView generation integration', () => { project: { projectId: input.projectId, title: '默认项目', + canvas: { revision: 1 }, viewport: { x: 0, y: 0, scale: 1 }, resources: [ ...projectResources, @@ -477,6 +478,7 @@ describe('ImageCanvasEditorView generation integration', () => { loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ projectId: 'editor-project-generated-dialog', title: '已生成画布', + canvas: { revision: 0 }, viewport: { x: 0, y: 0, scale: 1 }, layers: [ { @@ -546,6 +548,7 @@ describe('ImageCanvasEditorView generation integration', () => { loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ projectId: 'editor-project-generated-layer-only', title: '已生成画布', + canvas: { revision: 0 }, viewport: { x: 0, y: 0, scale: 1 }, layers: [ { @@ -600,6 +603,7 @@ describe('ImageCanvasEditorView generation integration', () => { loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ projectId: 'editor-project-uploaded-tagged-layer', title: '标签素材画布', + canvas: { revision: 0 }, viewport: { x: 0, y: 0, scale: 1 }, layers: [ { @@ -645,6 +649,7 @@ describe('ImageCanvasEditorView generation integration', () => { loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ projectId: 'editor-project-context-tagged-layer', title: '右键标签素材画布', + canvas: { revision: 0 }, viewport: { x: 0, y: 0, scale: 1 }, layers: [ { @@ -695,6 +700,7 @@ describe('ImageCanvasEditorView generation integration', () => { loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ projectId: 'editor-project-publication-dialog', title: '宣发画布', + canvas: { revision: 0 }, viewport: { x: 0, y: 0, scale: 1 }, layers: [ { @@ -1322,6 +1328,7 @@ describe('ImageCanvasEditorView generation integration', () => { loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ projectId: 'editor-project-icons-generating', title: '图标素材生成中画布', + canvas: { revision: 0 }, viewport: { x: 0, y: 0, scale: 1 }, layers: [ { @@ -1694,6 +1701,7 @@ describe('ImageCanvasEditorView generation integration', () => { loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ projectId: 'editor-project-default', title: '默认项目', + canvas: { revision: 0 }, viewport: { x: 0, y: 0, scale: 1 }, layers: rememberedProjectLayers, resources: rememberedProjectResources, @@ -2329,6 +2337,7 @@ describe('ImageCanvasEditorView generation integration', () => { loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ projectId: 'editor-project-default', title: '默认项目', + canvas: { revision: 0 }, viewport: { x: 0, y: 0, scale: 1 }, layers: characterReferenceProjectLayers, resources: characterReferenceProjectResources, @@ -2506,6 +2515,7 @@ describe('ImageCanvasEditorView generation integration', () => { loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ projectId: 'editor-project-icons', title: '图标素材画布', + canvas: { revision: 0 }, viewport: { x: 0, y: 0, scale: 1 }, layers: [ { @@ -2681,6 +2691,7 @@ describe('ImageCanvasEditorView generation integration', () => { loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ projectId: 'editor-project-icon-library-refresh', title: '图标素材刷新画布', + canvas: { revision: 0 }, viewport: { x: 0, y: 0, scale: 1 }, layers: [ { @@ -2826,6 +2837,7 @@ describe('ImageCanvasEditorView generation integration', () => { loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ projectId, title: '刷新恢复画布', + canvas: { revision: 0 }, viewport: { x: 0, y: 0, scale: 1 }, layers: [ { @@ -2868,6 +2880,7 @@ describe('ImageCanvasEditorView generation integration', () => { loadEditorProjectMock.mockResolvedValueOnce({ projectId, title: '刷新恢复画布', + canvas: { revision: 0 }, viewport: { x: 0, y: 0, scale: 1 }, layers: [ { @@ -3136,6 +3149,7 @@ describe('ImageCanvasEditorView generation integration', () => { loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ projectId: 'editor-project-character-animation', title: '角色动画画布', + canvas: { revision: 0 }, viewport: { x: 0, y: 0, scale: 1 }, layers: [ { @@ -3363,6 +3377,7 @@ describe('ImageCanvasEditorView generation integration', () => { loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ projectId: 'editor-project-ui-extract', title: 'UI素材提取画布', + canvas: { revision: 0 }, viewport: { x: 0, y: 0, scale: 1 }, layers: [ { @@ -3536,6 +3551,7 @@ describe('ImageCanvasEditorView generation integration', () => { loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({ projectId: 'editor-project-ui-extract-dismiss', title: 'UI素材提取外部关闭画布', + canvas: { revision: 0 }, viewport: { x: 0, y: 0, scale: 1 }, layers: [ { diff --git a/src/components/image-editor/ImageCanvasEditorView.test-utils.ts b/src/components/image-editor/ImageCanvasEditorView.test-utils.ts index 2d5082721..2fee555f1 100644 --- a/src/components/image-editor/ImageCanvasEditorView.test-utils.ts +++ b/src/components/image-editor/ImageCanvasEditorView.test-utils.ts @@ -4,6 +4,11 @@ import type { ContextType } from 'react'; import { afterEach, beforeEach, expect, type Mock, vi } from 'vitest'; import { ApiClientError } from '../../services/apiClient'; +import type { + EditorProjectLayerSnapshot, + EditorProjectResourceSnapshot, + EditorProjectSnapshot, +} from '../../services/image-editor/editorProjectClient'; import { AuthUiContext } from '../auth/AuthUiContext'; import { ImageCanvasEditorView } from './ImageCanvasEditorView'; @@ -73,7 +78,7 @@ export const defaultEditorProjectResources = [ height: 405, sourceType: 'uploaded', }, -]; +] satisfies EditorProjectResourceSnapshot[]; export const defaultEditorProjectLayers = [ { @@ -102,7 +107,31 @@ export const defaultEditorProjectLayers = [ zIndex: 2, sourceType: 'uploaded', }, -]; +] satisfies EditorProjectLayerSnapshot[]; + +export function withEditorProjectCanvasRevision< + T extends EditorProjectSnapshot, +>(project: T, revision = 0): T { + if (typeof project.canvas?.revision === 'number') { + return project; + } + return { + ...project, + canvas: { + canvasId: + project.canvas?.canvasId ?? `${project.projectId}:canvas:default`, + projectId: project.projectId, + title: project.canvas?.title ?? '默认画布', + viewport: project.canvas?.viewport ?? project.viewport, + layers: project.canvas?.layers ?? project.layers, + revision, + layoutStorageVersion: project.canvas?.layoutStorageVersion ?? 0, + backgroundColor: project.canvas?.backgroundColor, + createdAt: project.canvas?.createdAt, + updatedAt: project.canvas?.updatedAt ?? project.updatedAt, + }, + }; +} export const defaultEditorAssetLibraryAssets = [ { @@ -235,14 +264,14 @@ export function setupImageCanvasEditorViewTestLifecycle({ window.sessionStorage.clear(); vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue(null); loadOrCreateRecentEditorProjectMock.mockImplementation(() => - immediateAsync({ + immediateAsync(withEditorProjectCanvasRevision({ projectId: 'editor-project-default', title: '默认项目', viewport: { x: 0, y: 0, scale: 1 }, layers: defaultEditorProjectLayers, resources: defaultEditorProjectResources, updatedAt: '2026-06-12T00:00:00.000Z', - }), + })), ); loadEditorAssetLibraryMock.mockImplementation(() => immediateAsync({ diff --git a/src/components/image-editor/ImageCanvasEditorView.test.tsx b/src/components/image-editor/ImageCanvasEditorView.test.tsx index 9f0360f90..4c4a56e21 100644 --- a/src/components/image-editor/ImageCanvasEditorView.test.tsx +++ b/src/components/image-editor/ImageCanvasEditorView.test.tsx @@ -26,6 +26,7 @@ import { immediateAsync, readZipText, setupImageCanvasEditorViewTestLifecycle, + withEditorProjectCanvasRevision, } from './ImageCanvasEditorView.test-utils'; type EditorAgentListConversations = @@ -457,14 +458,14 @@ describe('ImageCanvasEditorView', () => { }); it('shows the toolbar guide for a newly created blank project until a generator opens', async () => { - loadEditorProjectMock.mockResolvedValueOnce({ + loadEditorProjectMock.mockResolvedValueOnce(withEditorProjectCanvasRevision({ projectId: 'editor-project-guide', title: '空白项目', viewport: { x: 0, y: 0, scale: 1 }, layers: [], resources: [], updatedAt: '2026-06-12T00:00:00.000Z', - }); + })); window.history.replaceState( null, '', @@ -505,14 +506,14 @@ describe('ImageCanvasEditorView', () => { }); it('opens the requested canvas startup tool from the project query once', async () => { - loadEditorProjectMock.mockResolvedValueOnce({ + loadEditorProjectMock.mockResolvedValueOnce(withEditorProjectCanvasRevision({ projectId: 'editor-project-music', title: '音乐项目', viewport: { x: 0, y: 0, scale: 1 }, layers: [], resources: [], updatedAt: '2026-06-12T00:00:00.000Z', - }); + })); window.history.replaceState( null, '',