Files
Genarrative/src/components/image-editor/ImageCanvasSelectedLayerToolbarView.test.tsx
T
JenkenB c836ac604d Treat character-action generations as video & UI tweaks
Handle character-action generation results as real video assets: use backend previewVideoPath as layer src, set mediaType="video" and assetKind="character-animation", and store the first frame only as thumbnail/poster. Update docs to document the new behavior and preserve backend frame extraction/green-screen workflows. Update UI/UX: compact parameter labels (e.g. "16:9·1K"), quick-edit treats original image as an implicit last reference (hidden from the reference bar) and allows editable bottom capsules, and ensure exports preserve true media file types. Change upload toolbar icon to Upload and add tests to cover the icon and video-layer behavior; also add serialization/hydration test for video thumbnails and adjust many integration/unit tests to reflect new labels, sizes and generation semantics. Files touched include multiple docs under docs/, edited components and tests under src/components/image-editor/, and minor stylesheet updates.
2026-06-22 13:45:20 +08:00

212 lines
7.1 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(),
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('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('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('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()}
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()}
onExtractUiDesignAssets={vi.fn()}
onOpenCharacterAnimationPanel={vi.fn()}
onDownloadLayer={vi.fn()}
/>,
);
expect(screen.queryByRole('toolbar', { name: '图片工具栏' })).toBeNull();
});
});