Files
Genarrative/src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx
kdletters d8b935317d 拆分编辑器前端画布视图
抽出素材栏、生成器、舞台工具栏和画布世界视图

补充各拆分视图的聚焦测试

更新 TRACKING.md 记录第三十四阶段验证
2026-06-17 17:48:12 +08:00

153 lines
5.4 KiB
TypeScript

/* @vitest-environment jsdom */
import { fireEvent, render, screen } from '@testing-library/react';
import { createRef, useState } from 'react';
import { describe, expect, it, vi } from 'vitest';
import type {
GenerateDialogState,
SpecGenerationType,
UploadTarget,
} from './ImageCanvasEditorTypes';
import { ImageCanvasCharacterGenerationComposerView } from './ImageCanvasCharacterGenerationComposerView';
function createDialog(
patch: Partial<GenerateDialogState> = {},
): GenerateDialogState {
return {
mode: 'character',
prompt: '旧角色设定',
status: 'idle',
characterSpecReference: {
id: 'spec-a',
label: '角色规范A',
src: 'data:image/png;base64,c3BlYw==',
},
characterReferences: [
{
id: 'ref-a',
label: '参考图A',
src: 'data:image/png;base64,cmVm',
},
],
...patch,
};
}
function CharacterGenerationHarness({
initialDialog = createDialog(),
onOpenSpecDialog = vi.fn(),
onRequestUpload = vi.fn(),
onSubmit = vi.fn(),
onRememberImageModel = vi.fn(),
}: {
initialDialog?: GenerateDialogState;
onOpenSpecDialog?: (specType: SpecGenerationType) => void;
onRequestUpload?: (target: UploadTarget) => void;
onSubmit?: (dialog: GenerateDialogState) => void;
onRememberImageModel?: (model: string) => void;
}) {
const [dialog, setDialog] = useState<GenerateDialogState | null>(
initialDialog,
);
const [isCharacterSpecMenuOpen, setIsCharacterSpecMenuOpen] =
useState(false);
const [
isCharacterReferenceMenuOpen,
setIsCharacterReferenceMenuOpen,
] = useState(false);
const [isPickingSpec, setIsPickingSpec] = useState(false);
const [isPickingReference, setIsPickingReference] = useState(false);
const characterSpecButtonRef = createRef<HTMLButtonElement>();
const characterReferenceButtonRef = createRef<HTMLButtonElement>();
return dialog ? (
<div>
<ImageCanvasCharacterGenerationComposerView
dialog={dialog}
style={{ left: 10, top: 20 }}
characterSpecButtonRef={characterSpecButtonRef}
characterReferenceButtonRef={characterReferenceButtonRef}
isCharacterSpecMenuOpen={isCharacterSpecMenuOpen}
isCharacterReferenceMenuOpen={isCharacterReferenceMenuOpen}
setGenerateDialog={setDialog}
setIsCharacterSpecMenuOpen={setIsCharacterSpecMenuOpen}
setIsCharacterReferenceMenuOpen={setIsCharacterReferenceMenuOpen}
setIsPickingCharacterSpecFromCanvas={setIsPickingSpec}
setIsPickingCharacterReferenceFromCanvas={setIsPickingReference}
renderEditorPortal={(node) => node}
buildPortalMenuStyle={() => ({ position: 'fixed', left: 0, top: 0 })}
onOpenSpecDialog={onOpenSpecDialog}
onRequestUpload={onRequestUpload}
onRememberImageModel={onRememberImageModel}
onSubmit={onSubmit}
/>
<output aria-label="当前角色设定">{dialog.prompt}</output>
<output aria-label="当前状态">{dialog.status}</output>
<output aria-label="当前错误">{dialog.errorMessage ?? '-'}</output>
<output aria-label="选择角色规范">{String(isPickingSpec)}</output>
<output aria-label="选择常规参考图">{String(isPickingReference)}</output>
</div>
) : null;
}
describe('ImageCanvasCharacterGenerationComposerView', () => {
it('updates character prompt, clears failed state and submits', () => {
const submitCharacter = vi.fn();
render(
<CharacterGenerationHarness
initialDialog={createDialog({
status: 'failed',
errorMessage: '角色失败',
})}
onSubmit={submitCharacter}
/>,
);
fireEvent.change(screen.getByLabelText('角色设定'), {
target: { value: '新的角色设定' },
});
fireEvent.click(screen.getByRole('button', { name: '生成' }));
expect(screen.getByLabelText('当前角色设定').textContent).toBe(
'新的角色设定',
);
expect(screen.getByLabelText('当前状态').textContent).toBe('idle');
expect(screen.getByLabelText('当前错误').textContent).toBe('-');
expect(submitCharacter).toHaveBeenCalledWith(
expect.objectContaining({ prompt: '新的角色设定', status: 'idle' }),
);
});
it('routes character reference menus to canvas picking, spec creation and upload', () => {
const openSpecDialog = vi.fn();
const requestUpload = vi.fn();
render(
<CharacterGenerationHarness
onOpenSpecDialog={openSpecDialog}
onRequestUpload={requestUpload}
/>,
);
fireEvent.click(screen.getByRole('button', { name: '角色规范A' }));
fireEvent.click(screen.getByRole('menuitem', { name: '从画布中选择' }));
expect(screen.getByLabelText('选择角色规范').textContent).toBe('true');
fireEvent.click(screen.getByRole('button', { name: '角色规范A' }));
fireEvent.click(screen.getByRole('menuitem', { name: '新建角色形象规范' }));
expect(openSpecDialog).toHaveBeenCalledWith('character');
fireEvent.click(screen.getByRole('button', { name: '角色规范A' }));
fireEvent.click(screen.getByRole('menuitem', { name: '上传图片' }));
expect(requestUpload).toHaveBeenCalledWith('character-spec');
fireEvent.click(screen.getByRole('button', { name: '上传常规参考图' }));
fireEvent.click(screen.getByRole('menuitem', { name: '从画布中选择' }));
expect(screen.getByLabelText('选择常规参考图').textContent).toBe('true');
});
});