拆分编辑器前端画布视图
抽出素材栏、生成器、舞台工具栏和画布世界视图 补充各拆分视图的聚焦测试 更新 TRACKING.md 记录第三十四阶段验证
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
/* @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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user