387a1c26e3
清理已跟踪原始日志与个人本地配置 修复前后端有效门禁、测试和构建问题 补齐生产 Jenkins、SpacetimeDB 本地命令与文档约束 收紧图片编辑器状态、附件与媒体引用测试 排除已下线旧创作入口测试并清理 warning
284 lines
7.9 KiB
TypeScript
284 lines
7.9 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { act,renderHook } from '@testing-library/react';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import type { CanvasGenerationDialogState } from './ImageCanvasEditorTypes';
|
|
import { useCanvasGenerationDialogs } from './useCanvasGenerationDialogs';
|
|
|
|
function createDialog(
|
|
mode: CanvasGenerationDialogState['mode'],
|
|
prompt: string,
|
|
): Omit<CanvasGenerationDialogState, 'id'> {
|
|
return {
|
|
mode,
|
|
prompt,
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
placeholder: {
|
|
x: mode === 'character' ? 30 : 10,
|
|
y: mode === 'character' ? 40 : 20,
|
|
width: 320,
|
|
height: 240,
|
|
originalWidth: 320,
|
|
originalHeight: 240,
|
|
},
|
|
};
|
|
}
|
|
|
|
describe('useCanvasGenerationDialogs', () => {
|
|
it('archives, activates, updates, and removes canvas generation dialogs', () => {
|
|
const onActivate = vi.fn();
|
|
const { result } = renderHook(() =>
|
|
useCanvasGenerationDialogs({ onActivate }),
|
|
);
|
|
|
|
act(() => {
|
|
result.current.openCanvasGenerationDialog(
|
|
createDialog('generate', 'first'),
|
|
);
|
|
});
|
|
expect(result.current.activeCanvasGenerationDialog).toEqual(
|
|
expect.objectContaining({
|
|
prompt: 'first',
|
|
composerOpen: true,
|
|
}),
|
|
);
|
|
|
|
act(() => {
|
|
result.current.openCanvasGenerationDialog(
|
|
createDialog('character', 'second'),
|
|
);
|
|
});
|
|
expect(result.current.activeCanvasGenerationDialog).toEqual(
|
|
expect.objectContaining({
|
|
prompt: 'second',
|
|
composerOpen: true,
|
|
}),
|
|
);
|
|
expect(result.current.inactiveGenerateDialogs).toEqual([
|
|
expect.objectContaining({
|
|
prompt: 'first',
|
|
composerOpen: false,
|
|
}),
|
|
]);
|
|
expect(
|
|
result.current.canvasGenerationDialogs.map((dialog) => dialog.prompt),
|
|
).toEqual(['first', 'second']);
|
|
|
|
const archivedFirstDialog = result.current.inactiveGenerateDialogs[0];
|
|
expect(archivedFirstDialog).toBeDefined();
|
|
|
|
act(() => {
|
|
result.current.activateCanvasGenerationDialog(archivedFirstDialog!);
|
|
});
|
|
expect(onActivate).toHaveBeenCalledTimes(1);
|
|
expect(result.current.activeCanvasGenerationDialog).toEqual(
|
|
expect.objectContaining({
|
|
prompt: 'first',
|
|
composerOpen: true,
|
|
}),
|
|
);
|
|
expect(result.current.inactiveGenerateDialogs).toEqual([
|
|
expect.objectContaining({
|
|
prompt: 'second',
|
|
composerOpen: false,
|
|
}),
|
|
]);
|
|
|
|
const activeDialog = result.current.activeCanvasGenerationDialog;
|
|
expect(activeDialog).toBeTruthy();
|
|
act(() => {
|
|
result.current.updateCanvasGenerationDialogById(
|
|
activeDialog!.id,
|
|
(dialog) => ({
|
|
...dialog,
|
|
status: 'generating',
|
|
placeholder: dialog.placeholder
|
|
? {
|
|
...dialog.placeholder,
|
|
x: 99,
|
|
}
|
|
: dialog.placeholder,
|
|
}),
|
|
);
|
|
});
|
|
expect(
|
|
result.current.getGeneratingDialogPlaceholder(activeDialog!)?.x,
|
|
).toBe(99);
|
|
|
|
act(() => {
|
|
result.current.updateCanvasGenerationDialogById(
|
|
activeDialog!.id,
|
|
(dialog) => ({
|
|
...dialog,
|
|
sourceLayerId: 'layer-a',
|
|
}),
|
|
);
|
|
});
|
|
act(() => {
|
|
result.current.removeCanvasGenerationDialogsByLayerId('layer-a');
|
|
});
|
|
expect(result.current.activeCanvasGenerationDialog).toBeNull();
|
|
expect(result.current.inactiveGenerateDialogs).toEqual([
|
|
expect.objectContaining({
|
|
prompt: 'second',
|
|
}),
|
|
]);
|
|
|
|
const archivedSecondDialog = result.current.inactiveGenerateDialogs[0];
|
|
expect(archivedSecondDialog).toBeDefined();
|
|
act(() => {
|
|
result.current.activateCanvasGenerationDialog(archivedSecondDialog!);
|
|
});
|
|
act(() => {
|
|
result.current.removeCanvasGenerationDialogById(archivedSecondDialog!.id);
|
|
});
|
|
expect(result.current.activeCanvasGenerationDialog).toBeNull();
|
|
expect(result.current.inactiveGenerateDialogs).toEqual([]);
|
|
});
|
|
|
|
it('restores saved dialogs and continues ids after the saved maximum', () => {
|
|
const { result } = renderHook(() => useCanvasGenerationDialogs());
|
|
|
|
act(() => {
|
|
result.current.restoreCanvasGenerationDialogs([
|
|
{
|
|
id: 'generation-dialog-12',
|
|
mode: 'generate',
|
|
prompt: 'restored',
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
placeholder: {
|
|
x: 12,
|
|
y: 24,
|
|
width: 320,
|
|
height: 240,
|
|
originalWidth: 320,
|
|
originalHeight: 240,
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
expect(result.current.activeCanvasGenerationDialog).toEqual(
|
|
expect.objectContaining({
|
|
id: 'generation-dialog-12',
|
|
prompt: 'restored',
|
|
}),
|
|
);
|
|
|
|
let nextDialogId = '';
|
|
act(() => {
|
|
nextDialogId = result.current.openCanvasGenerationDialog(
|
|
createDialog('character', 'second'),
|
|
);
|
|
});
|
|
expect(nextDialogId).toBe('generation-dialog-13');
|
|
expect(result.current.inactiveGenerateDialogs).toEqual([
|
|
expect.objectContaining({
|
|
id: 'generation-dialog-12',
|
|
prompt: 'restored',
|
|
composerOpen: false,
|
|
}),
|
|
]);
|
|
expect(result.current.activeCanvasGenerationDialog).toEqual(
|
|
expect.objectContaining({
|
|
id: 'generation-dialog-13',
|
|
prompt: 'second',
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('keeps the saved open dialog active when restoring multiple dialogs', () => {
|
|
const { result } = renderHook(() => useCanvasGenerationDialogs());
|
|
|
|
act(() => {
|
|
result.current.restoreCanvasGenerationDialogs([
|
|
{
|
|
id: 'generation-dialog-2',
|
|
mode: 'generate',
|
|
prompt: 'inactive saved',
|
|
status: 'idle',
|
|
composerOpen: false,
|
|
},
|
|
{
|
|
id: 'generation-dialog-3',
|
|
mode: 'character',
|
|
prompt: 'active saved',
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
},
|
|
]);
|
|
});
|
|
|
|
expect(result.current.activeCanvasGenerationDialog).toEqual(
|
|
expect.objectContaining({
|
|
id: 'generation-dialog-3',
|
|
prompt: 'active saved',
|
|
composerOpen: true,
|
|
}),
|
|
);
|
|
expect(result.current.inactiveGenerateDialogs).toEqual([
|
|
expect.objectContaining({
|
|
id: 'generation-dialog-2',
|
|
prompt: 'inactive saved',
|
|
composerOpen: false,
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it('keeps refs in sync when an archived dialog is activated and immediately updated', () => {
|
|
const { result } = renderHook(() => useCanvasGenerationDialogs());
|
|
|
|
act(() => {
|
|
result.current.openCanvasGenerationDialog(
|
|
createDialog('generate', 'first'),
|
|
);
|
|
});
|
|
act(() => {
|
|
result.current.openCanvasGenerationDialog(
|
|
createDialog('character', 'second'),
|
|
);
|
|
});
|
|
|
|
const archivedFirstDialog = result.current.inactiveGenerateDialogs[0];
|
|
expect(archivedFirstDialog).toEqual(
|
|
expect.objectContaining({ prompt: 'first' }),
|
|
);
|
|
|
|
let activeDialogRefId: string | undefined;
|
|
act(() => {
|
|
result.current.activateCanvasGenerationDialog(archivedFirstDialog!);
|
|
activeDialogRefId = result.current.generateDialogRef.current?.id;
|
|
result.current.updateCanvasGenerationDialogById(
|
|
archivedFirstDialog!.id,
|
|
(dialog) => ({
|
|
...dialog,
|
|
placeholder: dialog.placeholder
|
|
? {
|
|
...dialog.placeholder,
|
|
x: 128,
|
|
}
|
|
: dialog.placeholder,
|
|
}),
|
|
);
|
|
});
|
|
|
|
expect(activeDialogRefId).toBe(archivedFirstDialog!.id);
|
|
expect(result.current.activeCanvasGenerationDialog).toEqual(
|
|
expect.objectContaining({
|
|
id: archivedFirstDialog!.id,
|
|
prompt: 'first',
|
|
composerOpen: true,
|
|
placeholder: expect.objectContaining({ x: 128 }),
|
|
}),
|
|
);
|
|
expect(result.current.inactiveGenerateDialogs).toEqual([
|
|
expect.objectContaining({
|
|
prompt: 'second',
|
|
composerOpen: false,
|
|
}),
|
|
]);
|
|
});
|
|
});
|