Image editor: hide raw Prompt, use Resolution
Remove backend-assembled raw Prompt and copy action from image info; render a lightweight generationInputs snapshot (user panel inputs + reference thumbnails) stored on canvas layers and shown in the image info dialog. Unify canvas display and info to use originalWidth/originalHeight (Resolution) instead of saved Size and hydrate legacy layout width/height only as fallback. Add model/aspectRatio/imageSize options for character/icon generation (frontend state, tests, and client payloads). Increase Axum JSON body limit for character animation endpoint to 12MB for compatibility and prefer submitting persisted objectKey over large Data URLs. Update tests, docs, and related server/frontend code to reflect these behaviors and validations.
This commit is contained in:
@@ -66,29 +66,6 @@ function dispatchPointerEvent(
|
||||
fireEvent(target, event);
|
||||
}
|
||||
|
||||
function mockClipboard() {
|
||||
const originalClipboard = Object.getOwnPropertyDescriptor(
|
||||
navigator,
|
||||
'clipboard',
|
||||
);
|
||||
const writeText = vi.fn().mockResolvedValue(undefined);
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
configurable: true,
|
||||
value: { writeText },
|
||||
});
|
||||
|
||||
return {
|
||||
writeText,
|
||||
restore: () => {
|
||||
if (originalClipboard) {
|
||||
Object.defineProperty(navigator, 'clipboard', originalClipboard);
|
||||
} else {
|
||||
delete (navigator as unknown as { clipboard?: Clipboard }).clipboard;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('ImageCanvasEditorView', () => {
|
||||
beforeEach(() => {
|
||||
loadOrCreateRecentEditorProjectMock.mockResolvedValue({
|
||||
@@ -557,14 +534,14 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect(within(batchToolbar).getByText(/已选 2/u)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows image size on hover and placeholder toolbar after selecting a layer', () => {
|
||||
it('shows image resolution on hover and placeholder toolbar after selecting a layer', () => {
|
||||
const alertSpy = vi.spyOn(window, 'alert').mockImplementation(() => {});
|
||||
render(<ImageCanvasEditorView />);
|
||||
|
||||
const canvasImage = screen.getByAltText('画布图片:拼图素材');
|
||||
fireEvent.mouseEnter(canvasImage.closest('button')!);
|
||||
|
||||
const sizeBadge = screen.getByText('420 x 420 px');
|
||||
const sizeBadge = screen.getByText('640 x 640 px');
|
||||
expect(sizeBadge.className).toContain('rounded-full');
|
||||
expect(sizeBadge.className).toContain('image-canvas-editor__size-badge');
|
||||
|
||||
@@ -612,10 +589,14 @@ describe('ImageCanvasEditorView', () => {
|
||||
const infoPanel = screen.getByRole('dialog', { name: '拼图素材图片信息' });
|
||||
expect(within(infoPanel).getByText('图片类型')).toBeTruthy();
|
||||
expect(within(infoPanel).getByText('上传图片')).toBeTruthy();
|
||||
expect(within(infoPanel).getByText('Prompt')).toBeTruthy();
|
||||
expect(within(infoPanel).getByText('生成输入')).toBeTruthy();
|
||||
expect(
|
||||
infoPanel.querySelector('.image-canvas-editor__metadata-inputs')
|
||||
?.textContent,
|
||||
).toBe('-');
|
||||
expect(within(infoPanel).queryByText('Prompt')).toBeNull();
|
||||
expect(within(infoPanel).getByText('Model')).toBeTruthy();
|
||||
expect(within(infoPanel).getByText('Size')).toBeTruthy();
|
||||
expect(within(infoPanel).getByText('420 x 420 px')).toBeTruthy();
|
||||
expect(within(infoPanel).queryByText('Size')).toBeNull();
|
||||
expect(within(infoPanel).getByText('Resolution')).toBeTruthy();
|
||||
expect(within(infoPanel).getByText('640 x 640 px')).toBeTruthy();
|
||||
expect(
|
||||
@@ -624,6 +605,53 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect(screen.queryByRole('button', { name: '修改图片' })).toBeNull();
|
||||
});
|
||||
|
||||
it('hydrates canvas images from Resolution instead of saved Size', async () => {
|
||||
loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({
|
||||
projectId: 'editor-project-resolution',
|
||||
title: '原分辨率画布',
|
||||
viewport: { x: 0, y: 0, scale: 1 },
|
||||
layers: [
|
||||
{
|
||||
layerId: 'layer-resolution',
|
||||
resourceId: 'resource-resolution',
|
||||
title: '旧布局图片',
|
||||
src: 'data:image/png;base64,cmVzb2x1dGlvbg==',
|
||||
x: 120,
|
||||
y: 140,
|
||||
width: 320,
|
||||
height: 240,
|
||||
originalWidth: 1536,
|
||||
originalHeight: 1024,
|
||||
zIndex: 2,
|
||||
sourceType: 'generated',
|
||||
model: 'gpt-image-2',
|
||||
provider: 'VectorEngine',
|
||||
taskId: 'resolution-task-1',
|
||||
},
|
||||
],
|
||||
resources: [],
|
||||
updatedAt: '2026-06-16T00:00:00.000Z',
|
||||
});
|
||||
render(<ImageCanvasEditorView />);
|
||||
|
||||
const canvasImage = await screen.findByAltText('画布图片:旧布局图片');
|
||||
const canvasLayer = canvasImage.closest('button') as HTMLElement;
|
||||
expect(Number.parseFloat(canvasLayer.style.width)).toBe(1536);
|
||||
expect(Number.parseFloat(canvasLayer.style.height)).toBe(1024);
|
||||
|
||||
fireEvent.mouseEnter(canvasLayer);
|
||||
expect(screen.getByText('1536 x 1024 px')).toBeTruthy();
|
||||
fireEvent.click(
|
||||
screen.getAllByRole('button', { name: '查看旧布局图片图片信息' })[0]!,
|
||||
);
|
||||
const infoPanel = screen.getByRole('dialog', {
|
||||
name: '旧布局图片图片信息',
|
||||
});
|
||||
expect(within(infoPanel).queryByText('Size')).toBeNull();
|
||||
expect(within(infoPanel).getByText('Resolution')).toBeTruthy();
|
||||
expect(within(infoPanel).getByText('1536 x 1024 px')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('deletes the selected layer from the floating toolbar', () => {
|
||||
render(<ImageCanvasEditorView />);
|
||||
|
||||
@@ -795,9 +823,7 @@ describe('ImageCanvasEditorView', () => {
|
||||
).toBeTruthy();
|
||||
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: '缩放至100%' }));
|
||||
expect(
|
||||
screen.getByRole('button', { name: '当前缩放比例 100%' }),
|
||||
).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: /当前缩放比例 \d+%/u })).toBeTruthy();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '当前缩放比例 100%' }));
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: '缩放至50%' }));
|
||||
@@ -1117,6 +1143,18 @@ describe('ImageCanvasEditorView', () => {
|
||||
name: /查看生成图片 .*图片信息/,
|
||||
});
|
||||
expect(metadataButtons[0]).toBeTruthy();
|
||||
fireEvent.click(metadataButtons[0]!);
|
||||
|
||||
const infoPanel = screen.getByRole('dialog', {
|
||||
name: /生成图片 .*图片信息/,
|
||||
});
|
||||
expect(within(infoPanel).queryByText('Prompt')).toBeNull();
|
||||
expect(
|
||||
within(infoPanel).queryByRole('button', { name: '复制Prompt' }),
|
||||
).toBeNull();
|
||||
expect(within(infoPanel).getByText('生成输入')).toBeTruthy();
|
||||
expect(within(infoPanel).getByText('生成提示词')).toBeTruthy();
|
||||
expect(within(infoPanel).getByText('一张明亮的拼图主视觉')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('drags the generation placeholder and places the generated layer there', async () => {
|
||||
@@ -1163,6 +1201,13 @@ describe('ImageCanvasEditorView', () => {
|
||||
.top,
|
||||
);
|
||||
expect(draggedComposerTop).toBeGreaterThan(initialComposerTop);
|
||||
const draggedFrame = screen.getByLabelText('图像生成占位图') as HTMLElement;
|
||||
const draggedFrameCenterX =
|
||||
Number.parseFloat(draggedFrame.style.left) +
|
||||
Number.parseFloat(draggedFrame.style.width) / 2;
|
||||
const draggedFrameCenterY =
|
||||
Number.parseFloat(draggedFrame.style.top) +
|
||||
Number.parseFloat(draggedFrame.style.height) / 2;
|
||||
fireEvent.change(screen.getByLabelText('生成提示词'), {
|
||||
target: { value: '拖拽后的生成图' },
|
||||
});
|
||||
@@ -1186,11 +1231,13 @@ describe('ImageCanvasEditorView', () => {
|
||||
);
|
||||
expect(screen.queryByLabelText('图像生成占位图')).toBeNull();
|
||||
expect(
|
||||
Number.parseFloat((generatedLayer as HTMLElement).style.left),
|
||||
).toBeGreaterThan(300);
|
||||
Number.parseFloat((generatedLayer as HTMLElement).style.left) +
|
||||
Number.parseFloat((generatedLayer as HTMLElement).style.width) / 2,
|
||||
).toBeCloseTo(draggedFrameCenterX, 1);
|
||||
expect(
|
||||
Number.parseFloat((generatedLayer as HTMLElement).style.top),
|
||||
).toBeGreaterThan(180);
|
||||
Number.parseFloat((generatedLayer as HTMLElement).style.top) +
|
||||
Number.parseFloat((generatedLayer as HTMLElement).style.height) / 2,
|
||||
).toBeCloseTo(draggedFrameCenterY, 1);
|
||||
});
|
||||
|
||||
it('keeps the generation placeholder draggable while the image is generating', async () => {
|
||||
@@ -1264,11 +1311,21 @@ describe('ImageCanvasEditorView', () => {
|
||||
.getByAltText(/画布图片:生成图片/)
|
||||
.closest('button')!;
|
||||
expect(
|
||||
Number.parseFloat((generatedLayer as HTMLElement).style.left),
|
||||
).toBeGreaterThan(initialLeft);
|
||||
Number.parseFloat((generatedLayer as HTMLElement).style.left) +
|
||||
Number.parseFloat((generatedLayer as HTMLElement).style.width) / 2,
|
||||
).toBeCloseTo(
|
||||
Number.parseFloat((draggedFrame as HTMLElement).style.left) +
|
||||
Number.parseFloat((draggedFrame as HTMLElement).style.width) / 2,
|
||||
1,
|
||||
);
|
||||
expect(
|
||||
Number.parseFloat((generatedLayer as HTMLElement).style.top),
|
||||
).toBeGreaterThan(initialTop);
|
||||
Number.parseFloat((generatedLayer as HTMLElement).style.top) +
|
||||
Number.parseFloat((generatedLayer as HTMLElement).style.height) / 2,
|
||||
).toBeCloseTo(
|
||||
Number.parseFloat((draggedFrame as HTMLElement).style.top) +
|
||||
Number.parseFloat((draggedFrame as HTMLElement).style.height) / 2,
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
it('hides the generation composer when selecting another image but keeps the placeholder', () => {
|
||||
@@ -1677,6 +1734,116 @@ describe('ImageCanvasEditorView', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('defaults character and icon generation to nanobanana2 model options', async () => {
|
||||
render(<ImageCanvasEditorView />);
|
||||
await screen.findByAltText('画布图片:拼图素材');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '生成角色形象' }));
|
||||
const characterPanel = screen.getByRole('dialog', {
|
||||
name: '生成角色形象',
|
||||
});
|
||||
expect(within(characterPanel).getByText('尺寸比例')).toBeTruthy();
|
||||
expect(within(characterPanel).getByText('大小尺寸')).toBeTruthy();
|
||||
expect(
|
||||
(within(characterPanel).getByLabelText('角色模型') as HTMLSelectElement)
|
||||
.selectedOptions[0]?.textContent,
|
||||
).toBe('nanobanana2');
|
||||
expect(
|
||||
(
|
||||
within(characterPanel).getByLabelText(
|
||||
'角色尺寸比例',
|
||||
) as HTMLSelectElement
|
||||
).value,
|
||||
).toBe('1:1');
|
||||
expect(
|
||||
(
|
||||
within(characterPanel).getByLabelText(
|
||||
'角色大小尺寸',
|
||||
) as HTMLSelectElement
|
||||
).value,
|
||||
).toBe('1K');
|
||||
expect(
|
||||
Array.from(
|
||||
(
|
||||
within(characterPanel).getByLabelText(
|
||||
'角色尺寸比例',
|
||||
) as HTMLSelectElement
|
||||
).options,
|
||||
).map((option) => option.value),
|
||||
).toContain('1:8');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '生成图标素材' }));
|
||||
const iconPanel = screen.getByRole('dialog', { name: '生成图标素材' });
|
||||
expect(
|
||||
(within(iconPanel).getByLabelText('图标模型') as HTMLSelectElement)
|
||||
.selectedOptions[0]?.textContent,
|
||||
).toBe('nanobanana2');
|
||||
expect(
|
||||
(within(iconPanel).getByLabelText('图标大小尺寸') as HTMLSelectElement)
|
||||
.value,
|
||||
).toBe('1K');
|
||||
});
|
||||
|
||||
it('remembers the edited image model and submits character dimension options', async () => {
|
||||
generateEditorImageMock.mockResolvedValueOnce({
|
||||
imageSrc: 'data:image/png;base64,character-model-options',
|
||||
width: 1024,
|
||||
height: 1536,
|
||||
sourceType: 'generated',
|
||||
prompt: '高个子游侠',
|
||||
actualPrompt: '高个子游侠',
|
||||
model: 'gpt-image-2',
|
||||
provider: 'VectorEngine',
|
||||
taskId: 'character-model-options-1',
|
||||
});
|
||||
render(<ImageCanvasEditorView />);
|
||||
await screen.findByAltText('画布图片:拼图素材');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '生成角色形象' }));
|
||||
const characterPanel = screen.getByRole('dialog', {
|
||||
name: '生成角色形象',
|
||||
});
|
||||
fireEvent.change(within(characterPanel).getByLabelText('角色模型'), {
|
||||
target: { value: 'gpt-image-2' },
|
||||
});
|
||||
expect(
|
||||
Array.from(
|
||||
(
|
||||
within(characterPanel).getByLabelText(
|
||||
'角色尺寸比例',
|
||||
) as HTMLSelectElement
|
||||
).options,
|
||||
).map((option) => option.value),
|
||||
).not.toContain('1:8');
|
||||
fireEvent.change(within(characterPanel).getByLabelText('角色尺寸比例'), {
|
||||
target: { value: '2:3' },
|
||||
});
|
||||
fireEvent.change(within(characterPanel).getByLabelText('角色大小尺寸'), {
|
||||
target: { value: '1K' },
|
||||
});
|
||||
fireEvent.change(within(characterPanel).getByLabelText('角色设定'), {
|
||||
target: { value: '高个子游侠' },
|
||||
});
|
||||
fireEvent.click(within(characterPanel).getByRole('button', { name: '生成' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(generateEditorImageMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
kind: 'character',
|
||||
model: 'gpt-image-2',
|
||||
aspectRatio: '2:3',
|
||||
imageSize: '1K',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '生成图标素材' }));
|
||||
const iconPanel = screen.getByRole('dialog', { name: '生成图标素材' });
|
||||
expect(
|
||||
(within(iconPanel).getByLabelText('图标模型') as HTMLSelectElement).value,
|
||||
).toBe('gpt-image-2');
|
||||
});
|
||||
|
||||
it('keeps the bottom AI toolbar visible while generation panels are open', () => {
|
||||
render(<ImageCanvasEditorView />);
|
||||
|
||||
@@ -1806,12 +1973,16 @@ describe('ImageCanvasEditorView', () => {
|
||||
const generatedLayer = screen
|
||||
.getByAltText(/画布图片:生成图片/)
|
||||
.closest('button') as HTMLElement;
|
||||
const expectedLayerLeft =
|
||||
movedLeft + Number.parseFloat((movedFrame as HTMLElement).style.width) / 2 - 512;
|
||||
const expectedLayerTop =
|
||||
movedTop + Number.parseFloat((movedFrame as HTMLElement).style.height) / 2 - 512;
|
||||
expect(Number.parseFloat(generatedLayer.style.left)).toBeCloseTo(
|
||||
movedLeft,
|
||||
expectedLayerLeft,
|
||||
1,
|
||||
);
|
||||
expect(Number.parseFloat(generatedLayer.style.top)).toBeCloseTo(
|
||||
movedTop,
|
||||
expectedLayerTop,
|
||||
1,
|
||||
);
|
||||
expect(screen.getByLabelText('角色生成占位图')).toBeTruthy();
|
||||
@@ -2133,6 +2304,26 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect(screen.getByAltText(/画布图片:角色形象/u)).toBeTruthy();
|
||||
});
|
||||
expect(screen.getByText('角色')).toBeTruthy();
|
||||
fireEvent.click(
|
||||
screen.getAllByRole('button', {
|
||||
name: /查看角色形象 .*图片信息/u,
|
||||
})[0]!,
|
||||
);
|
||||
const characterInfoPanel = screen.getByRole('dialog', {
|
||||
name: /角色形象 .*图片信息/u,
|
||||
});
|
||||
expect(within(characterInfoPanel).queryByText('Prompt')).toBeNull();
|
||||
expect(within(characterInfoPanel).getByText('生成输入')).toBeTruthy();
|
||||
expect(within(characterInfoPanel).getByText('角色设定')).toBeTruthy();
|
||||
expect(
|
||||
within(characterInfoPanel).getByText(
|
||||
'银发游侠,蓝色披风,弓箭手,适合像素风战棋。',
|
||||
),
|
||||
).toBeTruthy();
|
||||
expect(within(characterInfoPanel).getByText('角色形象规范')).toBeTruthy();
|
||||
expect(within(characterInfoPanel).getByText('拼图素材')).toBeTruthy();
|
||||
expect(within(characterInfoPanel).getByText('常规参考图 1')).toBeTruthy();
|
||||
expect(within(characterInfoPanel).getByText('常规参考.png')).toBeTruthy();
|
||||
await waitFor(() => {
|
||||
expect(saveEditorProjectLayoutMock).toHaveBeenCalledWith(
|
||||
'editor-project-default',
|
||||
@@ -2338,6 +2529,20 @@ describe('ImageCanvasEditorView', () => {
|
||||
});
|
||||
expect(screen.queryByLabelText('图标素材生成占位图')).toBeNull();
|
||||
expect(screen.getAllByText('图标')).toHaveLength(2);
|
||||
fireEvent.click(
|
||||
screen.getAllByRole('button', { name: '查看返回按钮图片信息' })[0]!,
|
||||
);
|
||||
const iconInfoPanel = screen.getByRole('dialog', {
|
||||
name: '返回按钮图片信息',
|
||||
});
|
||||
expect(within(iconInfoPanel).queryByText('Prompt')).toBeNull();
|
||||
expect(within(iconInfoPanel).getByText('生成输入')).toBeTruthy();
|
||||
expect(within(iconInfoPanel).getByText('素材描述 1')).toBeTruthy();
|
||||
expect(within(iconInfoPanel).getByText('素材描述 2')).toBeTruthy();
|
||||
expect(within(iconInfoPanel).getByText('返回按钮')).toBeTruthy();
|
||||
expect(within(iconInfoPanel).getByText('设置按钮')).toBeTruthy();
|
||||
expect(within(iconInfoPanel).getByText('图标素材规范')).toBeTruthy();
|
||||
expect(within(iconInfoPanel).getByText('清爽按钮图标规范')).toBeTruthy();
|
||||
await waitFor(() => {
|
||||
expect(saveEditorProjectLayoutMock).toHaveBeenCalledWith(
|
||||
'editor-project-icons',
|
||||
@@ -2398,6 +2603,8 @@ describe('ImageCanvasEditorView', () => {
|
||||
originalHeight: 1024,
|
||||
zIndex: 2,
|
||||
sourceType: 'generated',
|
||||
objectKey:
|
||||
'generated-character-drafts/editor/character-images/source/image.png',
|
||||
assetKind: 'character',
|
||||
},
|
||||
{
|
||||
@@ -2514,7 +2721,8 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect(generateEditorCharacterAnimationMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sourceLayerId: 'layer-character',
|
||||
sourceImageSrc: 'data:image/png;base64,character',
|
||||
sourceImageSrc:
|
||||
'generated-character-drafts/editor/character-images/source/image.png',
|
||||
sourceWidth: 1024,
|
||||
sourceHeight: 1024,
|
||||
resolution: '720p',
|
||||
@@ -2628,10 +2836,10 @@ describe('ImageCanvasEditorView', () => {
|
||||
const generatedLayer = screen
|
||||
.getByAltText('画布图片:魔法森林 快速编辑')
|
||||
.closest('button') as HTMLElement;
|
||||
expect(Number.parseFloat(generatedLayer.style.left)).toBe(472);
|
||||
expect(Number.parseFloat(generatedLayer.style.left)).toBe(1688);
|
||||
expect(Number.parseFloat(generatedLayer.style.top)).toBe(140);
|
||||
expect(Number.parseFloat(generatedLayer.style.width)).toBe(320);
|
||||
expect(Number.parseFloat(generatedLayer.style.height)).toBe(240);
|
||||
expect(Number.parseFloat(generatedLayer.style.width)).toBe(1536);
|
||||
expect(Number.parseFloat(generatedLayer.style.height)).toBe(1024);
|
||||
await waitFor(() => {
|
||||
expect(saveEditorProjectLayoutMock).toHaveBeenCalledWith(
|
||||
'editor-project-quick-edit',
|
||||
@@ -2640,11 +2848,11 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect.objectContaining({
|
||||
title: '魔法森林 快速编辑',
|
||||
assetKind: 'spec',
|
||||
width: 320,
|
||||
height: 240,
|
||||
width: 1536,
|
||||
height: 1024,
|
||||
originalWidth: 1536,
|
||||
originalHeight: 1024,
|
||||
x: 472,
|
||||
x: 1688,
|
||||
y: 140,
|
||||
}),
|
||||
]),
|
||||
@@ -2940,8 +3148,7 @@ describe('ImageCanvasEditorView', () => {
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('opens generated image info from the corner button, copies Prompt and creates a real right-side edit result', async () => {
|
||||
const clipboard = mockClipboard();
|
||||
it('opens generated image info from the corner button and creates a real right-side edit result', async () => {
|
||||
generateEditorImageMock.mockResolvedValueOnce({
|
||||
imageSrc: 'data:image/png;base64,ZmFrZS1pbWFnZQ==',
|
||||
width: 1024,
|
||||
@@ -2975,13 +3182,17 @@ describe('ImageCanvasEditorView', () => {
|
||||
await waitFor(() => {
|
||||
expect(screen.getByAltText(/画布图片:生成图片/)).toBeTruthy();
|
||||
});
|
||||
const generatedLayer = screen
|
||||
.getByAltText(/画布图片:生成图片/)
|
||||
.closest('button') as HTMLElement;
|
||||
expect(Number.parseFloat(generatedLayer.style.width)).toBe(1024);
|
||||
expect(Number.parseFloat(generatedLayer.style.height)).toBe(1024);
|
||||
expect(screen.getByRole('dialog', { name: '生成图片' })).toBeTruthy();
|
||||
|
||||
const metadataCornerButton = screen.getAllByRole('button', {
|
||||
name: /查看生成图片 .*图片信息/,
|
||||
})[0];
|
||||
if (!metadataCornerButton) {
|
||||
clipboard.restore();
|
||||
throw new Error('metadata corner button should exist');
|
||||
}
|
||||
expect(metadataCornerButton.className).toContain('bg-black/55');
|
||||
@@ -2996,21 +3207,18 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect(metadataDialog).toBeTruthy();
|
||||
expect(within(metadataDialog).getByText('图片类型')).toBeTruthy();
|
||||
expect(within(metadataDialog).getByText('生成图片')).toBeTruthy();
|
||||
expect(within(metadataDialog).getByText('Prompt')).toBeTruthy();
|
||||
expect(within(metadataDialog).queryByText('Prompt')).toBeNull();
|
||||
expect(
|
||||
within(metadataDialog).queryByRole('button', { name: '复制Prompt' }),
|
||||
).toBeNull();
|
||||
expect(within(metadataDialog).getByText('生成输入')).toBeTruthy();
|
||||
expect(within(metadataDialog).getByText('生成提示词')).toBeTruthy();
|
||||
expect(within(metadataDialog).getByText('一张可修改的生成图')).toBeTruthy();
|
||||
expect(within(metadataDialog).getByText('Model')).toBeTruthy();
|
||||
expect(within(metadataDialog).getByText('gpt-image-2')).toBeTruthy();
|
||||
expect(within(metadataDialog).getByText('Size')).toBeTruthy();
|
||||
expect(within(metadataDialog).getByText('420 x 420 px')).toBeTruthy();
|
||||
expect(within(metadataDialog).queryByText('Size')).toBeNull();
|
||||
expect(within(metadataDialog).getByText('Resolution')).toBeTruthy();
|
||||
expect(within(metadataDialog).getByText('1024 x 1024 px')).toBeTruthy();
|
||||
fireEvent.click(
|
||||
within(metadataDialog).getByRole('button', { name: '复制Prompt' }),
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(clipboard.writeText).toHaveBeenCalledWith('一张可修改的生成图');
|
||||
});
|
||||
clipboard.restore();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '修改图片' }));
|
||||
const editDialog = screen.getByRole('dialog', { name: '修改图片' });
|
||||
@@ -3037,9 +3245,22 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect(screen.queryByRole('dialog', { name: '修改图片' })).toBeNull();
|
||||
});
|
||||
expect(screen.getByAltText(/画布图片:生成图片 .* 修改结果/)).toBeTruthy();
|
||||
fireEvent.click(
|
||||
screen.getAllByRole('button', {
|
||||
name: /查看生成图片 .* 修改结果图片信息/u,
|
||||
})[0]!,
|
||||
);
|
||||
const editedMetadataDialog = screen.getByRole('dialog', {
|
||||
name: /生成图片 .* 修改结果图片信息/u,
|
||||
});
|
||||
expect(within(editedMetadataDialog).queryByText('Prompt')).toBeNull();
|
||||
expect(within(editedMetadataDialog).getByText('修改要求')).toBeTruthy();
|
||||
expect(within(editedMetadataDialog).getByText('把画面改成黄昏光线')).toBeTruthy();
|
||||
expect(within(editedMetadataDialog).getByText('参考图')).toBeTruthy();
|
||||
expect(
|
||||
screen.getByRole('button', { name: '当前缩放比例 100%' }),
|
||||
within(editedMetadataDialog).getByText(/^生成图片 \d+$/u),
|
||||
).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: /当前缩放比例 \d+%/u })).toBeTruthy();
|
||||
});
|
||||
|
||||
it('hides the edit image panel after generation starts while keeping the source preview visible', async () => {
|
||||
|
||||
Reference in New Issue
Block a user