eae00292a1
生成图标图集后自动拆分并持久化独立素材 新增图集工具栏手动拆分入口与权威画布回填 修复连通域切片阅读顺序并补齐前后端测试 同步外部接口与编辑器设计文档
316 lines
10 KiB
TypeScript
316 lines
10 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import {
|
|
cleanup,
|
|
fireEvent,
|
|
render,
|
|
screen,
|
|
within,
|
|
} from '@testing-library/react';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import type { CanvasLayer } from './ImageCanvasEditorTypes';
|
|
import { ImageCanvasSelectedLayerToolbarView } from './ImageCanvasSelectedLayerToolbarView';
|
|
|
|
function createLayer(overrides: Partial<CanvasLayer> = {}): CanvasLayer {
|
|
return {
|
|
id: 'layer-1',
|
|
resourceId: 'resource-1',
|
|
title: '生成主图',
|
|
src: 'data:image/png;base64,layer',
|
|
x: 0,
|
|
y: 0,
|
|
width: 320,
|
|
height: 240,
|
|
originalWidth: 320,
|
|
originalHeight: 240,
|
|
zIndex: 1,
|
|
sourceType: 'uploaded',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function renderSelectedToolbar(
|
|
overrides: Partial<
|
|
Parameters<typeof ImageCanvasSelectedLayerToolbarView>[0]
|
|
> = {},
|
|
) {
|
|
const props: Parameters<typeof ImageCanvasSelectedLayerToolbarView>[0] = {
|
|
selectedLayer: createLayer(),
|
|
selectedToolbarStyle: { left: 12, top: 24 },
|
|
onOpenQuickEditPanel: vi.fn(),
|
|
onOpenRedrawPanel: vi.fn(),
|
|
onOpenCropExpandPanel: vi.fn(),
|
|
onRemoveBackground: vi.fn(),
|
|
onSplitIconSpritesheet: vi.fn(),
|
|
onExtractUiDesignAssets: vi.fn(),
|
|
onOpenCharacterAnimationPanel: vi.fn(),
|
|
onDownloadLayer: vi.fn(),
|
|
...overrides,
|
|
};
|
|
|
|
render(<ImageCanvasSelectedLayerToolbarView {...props} />);
|
|
|
|
return props;
|
|
}
|
|
|
|
describe('ImageCanvasSelectedLayerToolbarView', () => {
|
|
it('renders common layer actions in the Lovart toolbar order and forwards callbacks', () => {
|
|
const props = renderSelectedToolbar();
|
|
const toolbar = screen.getByRole('toolbar', { name: '图片工具栏' });
|
|
const buttons = within(toolbar).getAllByRole('button');
|
|
|
|
expect(buttons.map((button) => button.getAttribute('aria-label'))).toEqual([
|
|
'快速编辑',
|
|
'裁扩按钮',
|
|
'去除背景按钮',
|
|
'改造',
|
|
'下载按钮',
|
|
]);
|
|
|
|
fireEvent.click(within(toolbar).getByRole('button', { name: '快速编辑' }));
|
|
fireEvent.click(within(toolbar).getByRole('button', { name: '裁扩按钮' }));
|
|
fireEvent.click(
|
|
within(toolbar).getByRole('button', { name: '去除背景按钮' }),
|
|
);
|
|
fireEvent.click(within(toolbar).getByRole('button', { name: '改造' }));
|
|
fireEvent.click(within(toolbar).getByRole('button', { name: '下载按钮' }));
|
|
|
|
expect(props.onOpenQuickEditPanel).toHaveBeenCalledWith(
|
|
props.selectedLayer,
|
|
);
|
|
expect(props.onOpenCropExpandPanel).toHaveBeenCalledWith(
|
|
props.selectedLayer,
|
|
);
|
|
expect(props.onRemoveBackground).toHaveBeenCalledWith(props.selectedLayer);
|
|
expect(props.onOpenRedrawPanel).toHaveBeenCalledWith(props.selectedLayer);
|
|
expect(props.onDownloadLayer).toHaveBeenCalledWith(props.selectedLayer);
|
|
expect(
|
|
within(toolbar).queryByRole('button', { name: '删除图片' }),
|
|
).toBeNull();
|
|
expect(
|
|
within(toolbar).queryByRole('button', { name: '修改图片' }),
|
|
).toBeNull();
|
|
expect(
|
|
within(toolbar).queryByRole('button', { name: /查看.*图片信息/u }),
|
|
).toBeNull();
|
|
expect(
|
|
within(toolbar).queryByRole('button', { name: '提取素材' }),
|
|
).toBeNull();
|
|
expect(
|
|
within(toolbar)
|
|
.getByRole('button', { name: '去除背景按钮' })
|
|
.querySelector('.lucide-image-off'),
|
|
).toBeTruthy();
|
|
});
|
|
|
|
it('renders UI design asset extraction after remove background', () => {
|
|
const layer = createLayer({
|
|
sourceType: 'generated',
|
|
assetKind: 'ui-design',
|
|
});
|
|
const props = renderSelectedToolbar({ selectedLayer: layer });
|
|
const toolbar = screen.getByRole('toolbar', { name: '图片工具栏' });
|
|
const buttons = within(toolbar).getAllByRole('button');
|
|
|
|
expect(buttons.map((button) => button.getAttribute('aria-label'))).toEqual([
|
|
'快速编辑',
|
|
'裁扩按钮',
|
|
'去除背景按钮',
|
|
'提取素材',
|
|
'改造',
|
|
'下载按钮',
|
|
]);
|
|
|
|
fireEvent.click(within(toolbar).getByRole('button', { name: '提取素材' }));
|
|
|
|
expect(props.onExtractUiDesignAssets).toHaveBeenCalledWith(layer);
|
|
});
|
|
|
|
it('renders icon spritesheet splitting after remove background', () => {
|
|
const layer = createLayer({
|
|
sourceType: 'generated',
|
|
assetKind: 'icon-spritesheet',
|
|
});
|
|
const props = renderSelectedToolbar({ selectedLayer: layer });
|
|
const toolbar = screen.getByRole('toolbar', { name: '图片工具栏' });
|
|
const buttons = within(toolbar).getAllByRole('button');
|
|
|
|
expect(buttons.map((button) => button.getAttribute('aria-label'))).toEqual([
|
|
'裁扩按钮',
|
|
'去除背景按钮',
|
|
'拆分图集',
|
|
'改造',
|
|
'下载按钮',
|
|
]);
|
|
|
|
const splitButton = within(toolbar).getByRole('button', {
|
|
name: '拆分图集',
|
|
});
|
|
expect(splitButton.querySelector('.lucide-scissors')).toBeTruthy();
|
|
|
|
fireEvent.click(splitButton);
|
|
|
|
expect(props.onSplitIconSpritesheet).toHaveBeenCalledWith(layer);
|
|
});
|
|
|
|
it('keeps only remodel and download for audio layers', () => {
|
|
const layer = createLayer({
|
|
title: '游戏音效',
|
|
src: 'data:audio/mpeg;base64,audio',
|
|
mediaType: 'audio',
|
|
assetKind: 'sound-effect',
|
|
});
|
|
const props = renderSelectedToolbar({ selectedLayer: layer });
|
|
const toolbar = screen.getByRole('toolbar', { name: '素材工具栏' });
|
|
const buttons = within(toolbar).getAllByRole('button');
|
|
|
|
expect(buttons.map((button) => button.getAttribute('aria-label'))).toEqual([
|
|
'改造',
|
|
'下载按钮',
|
|
]);
|
|
expect(
|
|
within(toolbar).queryByRole('button', { name: '快速编辑' }),
|
|
).toBeNull();
|
|
expect(
|
|
within(toolbar).queryByRole('button', { name: '裁扩按钮' }),
|
|
).toBeNull();
|
|
expect(
|
|
within(toolbar).queryByRole('button', { name: '去除背景按钮' }),
|
|
).toBeNull();
|
|
expect(
|
|
within(toolbar).queryByRole('button', { name: '生成动画' }),
|
|
).toBeNull();
|
|
|
|
fireEvent.click(within(toolbar).getByRole('button', { name: '改造' }));
|
|
fireEvent.click(within(toolbar).getByRole('button', { name: '下载按钮' }));
|
|
|
|
expect(props.onOpenRedrawPanel).toHaveBeenCalledWith(layer);
|
|
expect(props.onDownloadLayer).toHaveBeenCalledWith(layer);
|
|
});
|
|
|
|
it('removes raster edit actions for video and character animation layers', () => {
|
|
renderSelectedToolbar({
|
|
selectedLayer: createLayer({
|
|
title: '生成视频',
|
|
src: '/generated/video.mp4',
|
|
mediaType: 'video',
|
|
assetKind: 'video',
|
|
}),
|
|
});
|
|
const videoToolbar = screen.getByRole('toolbar', { name: '图片工具栏' });
|
|
|
|
expect(
|
|
within(videoToolbar).queryByRole('button', { name: '裁扩按钮' }),
|
|
).toBeNull();
|
|
expect(
|
|
within(videoToolbar).queryByRole('button', { name: '去除背景按钮' }),
|
|
).toBeNull();
|
|
expect(
|
|
within(videoToolbar)
|
|
.getAllByRole('button')
|
|
.map((button) => button.getAttribute('aria-label')),
|
|
).toEqual(['快速编辑', '改造', '下载按钮']);
|
|
|
|
cleanup();
|
|
renderSelectedToolbar({
|
|
selectedLayer: createLayer({
|
|
title: '角色动作',
|
|
src: '/generated/action/frame-01.png',
|
|
mediaType: 'image-sequence',
|
|
assetKind: 'character-animation',
|
|
}),
|
|
});
|
|
const actionToolbar = screen.getByRole('toolbar', { name: '图片工具栏' });
|
|
|
|
expect(
|
|
within(actionToolbar).queryByRole('button', { name: '裁扩按钮' }),
|
|
).toBeNull();
|
|
expect(
|
|
within(actionToolbar).queryByRole('button', { name: '去除背景按钮' }),
|
|
).toBeNull();
|
|
expect(
|
|
within(actionToolbar)
|
|
.getAllByRole('button')
|
|
.map((button) => button.getAttribute('aria-label')),
|
|
).toEqual(['快速编辑', '改造', '下载按钮']);
|
|
});
|
|
|
|
it('renders character animation only for character layers', () => {
|
|
const layer = createLayer({
|
|
sourceType: 'generated',
|
|
assetKind: 'character',
|
|
});
|
|
const props = renderSelectedToolbar({ selectedLayer: layer });
|
|
const animationButton = screen.getByRole('button', { name: '生成动画' });
|
|
|
|
expect(animationButton.textContent).toContain('生成动画');
|
|
expect(
|
|
animationButton.querySelector('.lucide-person-standing'),
|
|
).toBeTruthy();
|
|
|
|
fireEvent.click(animationButton);
|
|
|
|
expect(props.onOpenCharacterAnimationPanel).toHaveBeenCalledWith(layer);
|
|
|
|
cleanup();
|
|
renderSelectedToolbar({ selectedLayer: createLayer({ id: 'layer-2' }) });
|
|
expect(screen.queryByRole('button', { name: '生成动画' })).toBeNull();
|
|
});
|
|
|
|
it.each(['icon', 'icon-spritesheet'] as const)(
|
|
'hides quick edit for %s assets',
|
|
(assetKind) => {
|
|
renderSelectedToolbar({
|
|
selectedLayer: createLayer({ assetKind }),
|
|
});
|
|
|
|
expect(screen.queryByRole('button', { name: '快速编辑' })).toBeNull();
|
|
},
|
|
);
|
|
|
|
it('keeps quick edit available for icon specs', () => {
|
|
renderSelectedToolbar({
|
|
selectedLayer: createLayer({ assetKind: 'icon-spec' }),
|
|
});
|
|
|
|
expect(screen.getByRole('button', { name: '快速编辑' })).toBeTruthy();
|
|
});
|
|
|
|
it('renders nothing without a selected layer or toolbar position', () => {
|
|
const { rerender } = render(
|
|
<ImageCanvasSelectedLayerToolbarView
|
|
selectedLayer={null}
|
|
selectedToolbarStyle={{ left: 0, top: 0 }}
|
|
onOpenQuickEditPanel={vi.fn()}
|
|
onOpenRedrawPanel={vi.fn()}
|
|
onOpenCropExpandPanel={vi.fn()}
|
|
onRemoveBackground={vi.fn()}
|
|
onSplitIconSpritesheet={vi.fn()}
|
|
onExtractUiDesignAssets={vi.fn()}
|
|
onOpenCharacterAnimationPanel={vi.fn()}
|
|
onDownloadLayer={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.queryByRole('toolbar', { name: '图片工具栏' })).toBeNull();
|
|
|
|
rerender(
|
|
<ImageCanvasSelectedLayerToolbarView
|
|
selectedLayer={createLayer()}
|
|
selectedToolbarStyle={null}
|
|
onOpenQuickEditPanel={vi.fn()}
|
|
onOpenRedrawPanel={vi.fn()}
|
|
onOpenCropExpandPanel={vi.fn()}
|
|
onRemoveBackground={vi.fn()}
|
|
onSplitIconSpritesheet={vi.fn()}
|
|
onExtractUiDesignAssets={vi.fn()}
|
|
onOpenCharacterAnimationPanel={vi.fn()}
|
|
onDownloadLayer={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.queryByRole('toolbar', { name: '图片工具栏' })).toBeNull();
|
|
});
|
|
});
|