/* @vitest-environment jsdom */ import { fireEvent, render, screen, within } from '@testing-library/react'; import { createRef } from 'react'; import { describe, expect, it, vi } from 'vitest'; import type { CanvasLayer, EditorAsset, EditorAssetFolder, } from './ImageCanvasEditorTypes'; import { ImageCanvasPanelDockView } from './ImageCanvasPanelDockView'; import { ImageCanvasSidebarView, type ImageCanvasSidebarViewProps, } from './ImageCanvasSidebarView'; import { useImageCanvasEditorChrome } from './useImageCanvasEditorChrome'; function createAsset(overrides: Partial = {}): 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 { return { id: 'project', label: '项目素材', collapsed: false, systemDefault: true, persisted: true, ...overrides, }; } function createLayer(overrides: Partial = {}): 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 { const folder = createFolder(); const asset = createAsset(); return { activeSidebarPanel: 'assets', assetListRef: createRef(), 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, isExportingSelectedAssets: false, assetMarquee: null, setCreatingFolder: vi.fn(), setNewFolderName: vi.fn(), setRenamingFolder: vi.fn(), setRenamingAsset: vi.fn(), setActiveUploadFolderId: vi.fn(), setUploadDropTarget: vi.fn(), setAssetPointerDrag: 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(), reconcileSelectedAssets: vi.fn(), downloadSelectedAssets: vi.fn(), deleteSelectedAssets: vi.fn(), openAssetSelectionMode: 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 (
); } describe('ImageCanvasSidebarView', () => { it('renders the asset library folders and asset actions', () => { render(); 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( , ); 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( , ); 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('keeps the batch toolbar outside the scrolling list and downloads visible selections', () => { const downloadSelectedAssets = vi.fn(); const closeAssetSelectionMode = vi.fn(); const asset = createAsset(); render( , ); const toolbar = screen.getByRole('toolbar', { name: '素材批量操作' }); const sidebar = screen.getByRole('complementary', { name: '图片资源栏' }); const assetList = document.querySelector( '.image-canvas-editor__asset-list', ); expect(sidebar.className).toContain( 'image-canvas-editor__sidebar--asset-selection', ); expect(toolbar.parentElement).toBe(assetList?.parentElement); expect(assetList?.contains(toolbar)).toBe(false); expect( screen.queryByRole('button', { name: '下载素材账号素材A' }), ).toBeNull(); fireEvent.click(within(toolbar).getByRole('button', { name: '下载' })); expect(downloadSelectedAssets).toHaveBeenCalledWith(['asset-1']); fireEvent.click(screen.getByRole('button', { name: '素材选择模式' })); expect(closeAssetSelectionMode).toHaveBeenCalledTimes(1); }); it('opens selection mode through the shared lifecycle and reconciles search visibility', () => { const openAssetSelectionMode = vi.fn(); const reconcileSelectedAssets = vi.fn(); const folder = createFolder(); render( , ); fireEvent.click(screen.getByRole('button', { name: '素材选择模式' })); expect(openAssetSelectionMode).toHaveBeenCalledTimes(1); expect(reconcileSelectedAssets).not.toHaveBeenCalled(); }); it('renders the layer panel with grouped and locked layer details', () => { render( , ); 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( , ); 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( , ); 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(); 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(); }); });