Files
Genarrative/src/components/image-editor/ImageCanvasSidebarView.test.tsx
T
kdletters b8e6e9f3ef 控制画布 Agent 入口默认关闭
新增 VITE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR 控制画布 Agent 入口显示。

默认 .env 关闭、本地 .env.local 开启,并补充示例和类型声明。

禁用时隐藏 dock 按钮和收起态 Agent 面板,开启时保留右侧任务列表互斥逻辑。

修正右侧 Agent 和任务列表切换不影响左侧资源栏。

补充画布 Agent 开关、面板独立性和 dock 隐藏测试。
2026-07-06 18:51:11 +08:00

373 lines
12 KiB
TypeScript

/* @vitest-environment jsdom */
import { fireEvent, render, screen, within } from '@testing-library/react';
import { createRef } from 'react';
import { describe, expect, it, vi } from 'vitest';
import { ImageCanvasPanelDockView } from './ImageCanvasPanelDockView';
import {
ImageCanvasSidebarView,
type ImageCanvasSidebarViewProps,
} from './ImageCanvasSidebarView';
import type {
CanvasLayer,
EditorAsset,
EditorAssetFolder,
} from './ImageCanvasEditorTypes';
import { useImageCanvasEditorChrome } from './useImageCanvasEditorChrome';
function createAsset(overrides: Partial<EditorAsset> = {}): EditorAsset {
return {
id: 'asset-1',
label: '账号素材A',
src: '/creation-type-references/puzzle.webp',
width: 640,
height: 640,
folderId: 'project',
sourceKind: 'uploaded',
sourceType: 'uploaded',
persisted: true,
...overrides,
};
}
function createFolder(
overrides: Partial<EditorAssetFolder> = {},
): EditorAssetFolder {
return {
id: 'project',
label: '项目素材',
collapsed: false,
systemDefault: true,
persisted: true,
...overrides,
};
}
function createLayer(overrides: Partial<CanvasLayer> = {}): CanvasLayer {
return {
id: 'layer-1',
resourceId: 'resource-1',
title: '图层A',
src: '/creation-type-references/puzzle.webp',
x: 0,
y: 0,
width: 320,
height: 240,
originalWidth: 320,
originalHeight: 240,
zIndex: 1,
sourceType: 'uploaded',
...overrides,
};
}
function createSidebarProps(
overrides: Partial<ImageCanvasSidebarViewProps> = {},
): ImageCanvasSidebarViewProps {
const folder = createFolder();
const asset = createAsset();
return {
activeSidebarPanel: 'assets',
assetListRef: createRef<HTMLDivElement>(),
assetPointerDragRef: { current: null },
suppressAssetClickRef: { current: false },
assets: [asset],
groupedAssets: [{ ...folder, assets: [asset] }],
assetFolders: [folder],
layers: [createLayer()],
selectedLayerId: null,
selectedLayerIds: [],
isAssetSelectionMode: false,
selectedAssetIds: new Set(),
assetMoveDropFolderId: null,
pinnedAssetMoveFolderId: null,
creatingFolder: false,
newFolderName: '',
renamingFolder: null,
renamingAsset: null,
allSelectableAssetsSelected: false,
assetMarquee: null,
setIsAssetSelectionMode: vi.fn(),
setCreatingFolder: vi.fn(),
setNewFolderName: vi.fn(),
setRenamingFolder: vi.fn(),
setRenamingAsset: vi.fn(),
setActiveUploadFolderId: vi.fn(),
setUploadDropTarget: vi.fn(),
setAssetPointerDrag: vi.fn(),
setSelectedAssetIds: vi.fn(),
setImageContextMenu: vi.fn(),
setContextMenu: vi.fn(),
onAssetMarqueePointerDown: vi.fn(),
onAssetMarqueePointerMove: vi.fn(),
onAssetMarqueePointerUp: vi.fn(),
updateAssetMoveDropFolder: vi.fn(),
addUploadedFiles: vi.fn(),
requestUpload: vi.fn(),
moveAssetToFolder: vi.fn(),
commitNewAssetFolder: vi.fn(),
toggleAssetFolder: vi.fn(),
startRenamingFolder: vi.fn(),
commitFolderRename: vi.fn(),
deleteAssetFolder: vi.fn(),
startRenamingAsset: vi.fn(),
commitAssetRename: vi.fn(),
deleteUploadedAsset: vi.fn(),
submitAssetToShowcase: vi.fn(),
toggleAssetSelected: vi.fn(),
addAssetLayer: vi.fn(),
onDownloadAsset: vi.fn(),
toggleAllAssetsSelected: vi.fn(),
deleteSelectedAssets: vi.fn(),
closeAssetSelectionMode: vi.fn(),
groupSelectedLayers: vi.fn(),
selectSingleLayer: vi.fn(),
resolveContextMenuPosition: vi.fn(() => ({ x: 0, y: 0 })),
getCanvasPointFromClient: vi.fn(() => ({ x: 0, y: 0 })),
...overrides,
};
}
function SidebarTabsHarness() {
const chrome = useImageCanvasEditorChrome({ openEditorLoginModal: vi.fn() });
const asset = createAsset();
const folder = createFolder();
const layer = createLayer({ title: '图层B', zIndex: 2 });
const sidebarProps = createSidebarProps({
activeSidebarPanel: chrome.activeSidebarPanel,
assets: [asset],
groupedAssets: [{ ...folder, assets: [asset] }],
assetFolders: [folder],
layers: [layer],
selectedLayerId: null,
selectedLayerIds: [],
});
return (
<div>
<ImageCanvasSidebarView {...sidebarProps} />
<ImageCanvasPanelDockView
viewport={{ x: 0, y: 0, scale: 1 }}
canvasBackgroundColor={chrome.canvasBackgroundColor}
canvasBackgroundHexValue={chrome.canvasBackgroundHexValue}
canUndo={false}
canRedo={false}
isZoomMenuOpen={false}
isBackgroundSettingsOpen={false}
activeSidebarPanel={chrome.activeSidebarPanel}
isAgentConversationEnabled={false}
isAgentConversationOpen={false}
isMinimapOpen={false}
minimapModel={null}
onFitLayers={vi.fn()}
onUndoCanvasChange={vi.fn()}
onRedoCanvasChange={vi.fn()}
onUpdateScaleFromCenter={vi.fn()}
onToggleZoomMenu={vi.fn()}
onCloseZoomMenu={vi.fn()}
onToggleBackgroundSettings={vi.fn()}
onApplyCanvasBackgroundColor={vi.fn()}
onCanvasBackgroundHexChange={vi.fn()}
onToggleSidebarPanel={chrome.toggleSidebarPanel}
onToggleAgentConversation={vi.fn()}
onToggleMinimap={vi.fn()}
onMinimapPointerDown={vi.fn()}
/>
</div>
);
}
describe('ImageCanvasSidebarView', () => {
it('renders the asset library folders and asset actions', () => {
render(<ImageCanvasSidebarView {...createSidebarProps()} />);
const sidebar = screen.getByRole('complementary', { name: '图片资源栏' });
const folder = within(sidebar).getByRole('region', { name: '项目素材' });
expect(within(sidebar).getByRole('heading', { name: '素材' })).toBeTruthy();
expect(
within(folder).getByRole('button', { name: '添加账号素材A' }),
).toBeTruthy();
expect(
within(folder).getByRole('button', { name: '上传到项目素材' }),
).toBeTruthy();
expect(
within(folder).getByRole('button', { name: '下载素材账号素材A' }),
).toBeTruthy();
});
it('renders asset editing inputs with sidebar text-field chrome', () => {
const folder = createFolder();
const asset = createAsset();
render(
<ImageCanvasSidebarView
{...createSidebarProps({
assets: [asset],
groupedAssets: [{ ...folder, assets: [asset] }],
assetFolders: [folder],
creatingFolder: true,
newFolderName: '角色上传',
renamingFolder: { folderId: folder.id, value: '角色参考' },
renamingAsset: { assetId: asset.id, value: '主视觉素材' },
})}
/>,
);
const folderNameInput = screen.getByLabelText('素材文件夹名称');
const folderRenameInput = screen.getByLabelText('重命名文件夹项目素材');
const assetRenameInput = screen.getByLabelText('重命名素材账号素材A');
expect(folderNameInput.className).toContain('platform-text-field');
expect(folderNameInput.className).toContain(
'image-canvas-editor__folder-create-input',
);
expect(folderRenameInput.className).toContain('platform-text-field');
expect(folderRenameInput.className).toContain(
'image-canvas-editor__folder-rename-input',
);
expect(assetRenameInput.className).toContain('platform-text-field');
expect(assetRenameInput.className).toContain(
'image-canvas-editor__asset-rename-input',
);
});
it('filters assets from the sidebar search field', () => {
const folder = createFolder();
render(
<ImageCanvasSidebarView
{...createSidebarProps({
assets: [
createAsset({ id: 'asset-a', label: '账号素材A' }),
createAsset({ id: 'asset-b', label: '火焰按钮' }),
],
groupedAssets: [
{
...folder,
assets: [
createAsset({ id: 'asset-a', label: '账号素材A' }),
createAsset({ id: 'asset-b', label: '火焰按钮' }),
],
},
],
})}
/>,
);
const sidebar = screen.getByRole('complementary', { name: '图片资源栏' });
fireEvent.change(within(sidebar).getByLabelText('搜索素材'), {
target: { value: '火焰' },
});
expect(
within(sidebar).getByRole('button', { name: '添加火焰按钮' }),
).toBeTruthy();
expect(
within(sidebar).queryByRole('button', { name: '添加账号素材A' }),
).toBeNull();
});
it('renders the layer panel with grouped and locked layer details', () => {
render(
<ImageCanvasSidebarView
{...createSidebarProps({
activeSidebarPanel: 'layers',
layers: [
createLayer({
title: '图层A',
groupId: 'group-1',
locked: true,
zIndex: 3,
}),
],
selectedLayerId: 'layer-1',
})}
/>,
);
const sidebar = screen.getByRole('complementary', { name: '图片资源栏' });
expect(within(sidebar).getByRole('heading', { name: '图层' })).toBeTruthy();
expect(
within(sidebar).getByRole('button', { name: '选择图层图层A' }),
).toBeTruthy();
expect(
within(sidebar).getByText('320 x 240 · 已打组 · 已锁定'),
).toBeTruthy();
});
it('uses the transparent audio cover for audio layer thumbnails', () => {
render(
<ImageCanvasSidebarView
{...createSidebarProps({
activeSidebarPanel: 'layers',
layers: [
createLayer({
title: '胜利音效',
src: '/generated-character-drafts/editor-audios/win.mp3',
mediaType: 'audio',
}),
],
})}
/>,
);
const image = screen.getByAltText('图层缩略图:胜利音效');
expect(image.getAttribute('src')).toBe(
'/creation-home/audio-asset-cover.png',
);
});
it('filters canvas layers from the sidebar search field', () => {
render(
<ImageCanvasSidebarView
{...createSidebarProps({
activeSidebarPanel: 'layers',
layers: [
createLayer({ id: 'layer-a', title: '角色草图', zIndex: 3 }),
createLayer({ id: 'layer-b', title: '背景山脉', zIndex: 2 }),
],
})}
/>,
);
const sidebar = screen.getByRole('complementary', { name: '图片资源栏' });
fireEvent.change(within(sidebar).getByLabelText('搜索画布图层'), {
target: { value: '背景' },
});
expect(
within(sidebar).getByRole('button', { name: '选择图层背景山脉' }),
).toBeTruthy();
expect(
within(sidebar).queryByRole('button', { name: '选择图层角色草图' }),
).toBeNull();
});
it('keeps sidebar tab switching and repeated-click closing behavior', () => {
render(<SidebarTabsHarness />);
expect(
screen.queryByRole('complementary', { name: '图片资源栏' }),
).toBeNull();
fireEvent.click(screen.getByRole('button', { name: '打开素材' }));
expect(screen.getByRole('heading', { name: '素材' })).toBeTruthy();
expect(screen.getByRole('button', { name: '添加账号素材A' })).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: '打开图层' }));
expect(screen.getByRole('heading', { name: '图层' })).toBeTruthy();
expect(screen.getByRole('button', { name: '选择图层图层B' })).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: '打开图层' }));
expect(
screen.queryByRole('complementary', { name: '图片资源栏' }),
).toBeNull();
});
});