8dad52f040
新增画布Agent会话契约、领域规则、SpacetimeDB元数据表和生成绑定。 新增api-server画布Agent会话CRUD、OSS消息文档读写、SSE消息流和生成工具编排。 新增前端右侧Agent对话面板、会话状态hook、SSE客户端、BFF客户端和相关测试。 调整画布侧栏、任务栏、小地图默认状态和面板互斥交互。 补齐画布Agent、OSS消息存储、SSE收口、后端契约和项目记忆文档。
154 lines
4.9 KiB
TypeScript
154 lines
4.9 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { act, renderHook, waitFor } from '@testing-library/react';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { ApiClientError } from '../../services/apiClient';
|
|
import { useImageCanvasEditorChrome } from './useImageCanvasEditorChrome';
|
|
|
|
const renameEditorProjectMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock('../../services/image-editor/editorProjectClient', async () => {
|
|
const actual = await vi.importActual<
|
|
typeof import('../../services/image-editor/editorProjectClient')
|
|
>('../../services/image-editor/editorProjectClient');
|
|
return {
|
|
...actual,
|
|
renameEditorProject: renameEditorProjectMock,
|
|
};
|
|
});
|
|
|
|
function renderChrome({
|
|
openEditorLoginModal = vi.fn(),
|
|
}: {
|
|
openEditorLoginModal?: (postLoginAction?: (() => void) | null) => void;
|
|
} = {}) {
|
|
return renderHook(() => useImageCanvasEditorChrome({ openEditorLoginModal }));
|
|
}
|
|
|
|
describe('useImageCanvasEditorChrome', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('renames projects and resets rename state from the saved title', async () => {
|
|
renameEditorProjectMock.mockResolvedValueOnce({
|
|
projectId: 'project-1',
|
|
title: '后端项目名',
|
|
viewport: { x: 0, y: 0, scale: 1 },
|
|
layers: [],
|
|
resources: [],
|
|
updatedAt: '2026-06-17T00:00:00.000Z',
|
|
});
|
|
const { result } = renderChrome();
|
|
|
|
act(() => {
|
|
result.current.setProjectTitle('已有项目');
|
|
});
|
|
act(() => {
|
|
result.current.startProjectRename();
|
|
});
|
|
expect(result.current.projectRenameValue).toBe('已有项目');
|
|
|
|
act(() => {
|
|
result.current.setProjectRenameValue(' 新项目 ');
|
|
});
|
|
act(() => {
|
|
result.current.submitProjectRename('project-1');
|
|
});
|
|
expect(result.current.isProjectRenameSaving).toBe(true);
|
|
|
|
await waitFor(() => {
|
|
expect(renameEditorProjectMock).toHaveBeenCalledWith(
|
|
'project-1',
|
|
'新项目',
|
|
);
|
|
});
|
|
await waitFor(() => {
|
|
expect(result.current.isProjectRenameSaving).toBe(false);
|
|
});
|
|
expect(result.current.projectTitle).toBe('后端项目名');
|
|
expect(result.current.projectRenameValue).toBe('后端项目名');
|
|
expect(result.current.isRenamingProject).toBe(false);
|
|
});
|
|
|
|
it('validates rename input and opens login on rename auth errors', async () => {
|
|
const openEditorLoginModal = vi.fn();
|
|
renameEditorProjectMock.mockRejectedValueOnce(
|
|
new ApiClientError({
|
|
message: '未授权访问',
|
|
status: 401,
|
|
code: 'UNAUTHORIZED',
|
|
}),
|
|
);
|
|
const { result } = renderChrome({ openEditorLoginModal });
|
|
|
|
act(() => {
|
|
result.current.setProjectRenameValue(' ');
|
|
});
|
|
act(() => {
|
|
result.current.submitProjectRename('project-1');
|
|
});
|
|
expect(result.current.projectRenameError).toBe('项目名称不能为空');
|
|
expect(renameEditorProjectMock).not.toHaveBeenCalled();
|
|
|
|
act(() => {
|
|
result.current.setProjectRenameValue('新项目');
|
|
});
|
|
act(() => {
|
|
result.current.submitProjectRename('project-1');
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(openEditorLoginModal).toHaveBeenCalledTimes(1);
|
|
});
|
|
expect(result.current.projectRenameError).toBe('未授权访问');
|
|
});
|
|
|
|
it('manages background colors and chrome panel toggles', () => {
|
|
const { result } = renderChrome();
|
|
|
|
expect(result.current.activeSidebarPanel).toBeNull();
|
|
expect(result.current.isMinimapOpen).toBe(false);
|
|
|
|
act(() => {
|
|
result.current.applyCanvasBackgroundColor('#abc');
|
|
});
|
|
expect(result.current.canvasBackgroundColor).toBe('#aabbcc');
|
|
expect(result.current.canvasBackgroundHexValue).toBe('#aabbcc');
|
|
|
|
act(() => {
|
|
result.current.handleCanvasBackgroundHexChange('#not-a-color');
|
|
});
|
|
expect(result.current.canvasBackgroundColor).toBe('#aabbcc');
|
|
expect(result.current.canvasBackgroundHexValue).toBe('#not-a-color');
|
|
|
|
act(() => {
|
|
result.current.handleCanvasBackgroundHexChange('#ffffff');
|
|
});
|
|
expect(result.current.canvasBackgroundColor).toBe('#ffffff');
|
|
expect(result.current.canvasBackgroundHexValue).toBe('#ffffff');
|
|
|
|
act(() => {
|
|
result.current.toggleSidebarPanel('assets');
|
|
result.current.toggleSidebarPanel('layers');
|
|
result.current.toggleZoomMenu();
|
|
result.current.toggleBackgroundSettings();
|
|
result.current.toggleMinimap();
|
|
result.current.setActiveTool('hand');
|
|
});
|
|
expect(result.current.activeSidebarPanel).toBe('layers');
|
|
expect(result.current.isZoomMenuOpen).toBe(true);
|
|
expect(result.current.isBackgroundSettingsOpen).toBe(true);
|
|
expect(result.current.isMinimapOpen).toBe(true);
|
|
expect(result.current.activeTool).toBe('hand');
|
|
|
|
act(() => {
|
|
result.current.closeEditorChromePanels();
|
|
});
|
|
expect(result.current.activeSidebarPanel).toBeNull();
|
|
expect(result.current.isZoomMenuOpen).toBe(false);
|
|
expect(result.current.isBackgroundSettingsOpen).toBe(false);
|
|
});
|
|
});
|