修复图片画布新增素材持久化
新增画布图层资源创建后的即时布局保存 补充素材库图片加入画布的持久化回归测试 更新图片画布回归验证记录
This commit is contained in:
@@ -242,26 +242,26 @@ describe('ImageCanvasEditorView', () => {
|
||||
beforeEach(() => {
|
||||
loadOrCreateRecentEditorProjectMock.mockImplementation(() =>
|
||||
immediateAsync({
|
||||
projectId: 'editor-project-default',
|
||||
title: '默认项目',
|
||||
viewport: { x: 0, y: 0, scale: 1 },
|
||||
layers: defaultEditorProjectLayers,
|
||||
resources: defaultEditorProjectResources,
|
||||
updatedAt: '2026-06-12T00:00:00.000Z',
|
||||
projectId: 'editor-project-default',
|
||||
title: '默认项目',
|
||||
viewport: { x: 0, y: 0, scale: 1 },
|
||||
layers: defaultEditorProjectLayers,
|
||||
resources: defaultEditorProjectResources,
|
||||
updatedAt: '2026-06-12T00:00:00.000Z',
|
||||
}),
|
||||
);
|
||||
loadEditorAssetLibraryMock.mockImplementation(() =>
|
||||
immediateAsync({
|
||||
folders: [
|
||||
{
|
||||
folderId: 'project',
|
||||
label: '项目素材',
|
||||
sortOrder: 0,
|
||||
collapsed: false,
|
||||
systemDefault: true,
|
||||
},
|
||||
],
|
||||
assets: defaultEditorAssetLibraryAssets,
|
||||
folders: [
|
||||
{
|
||||
folderId: 'project',
|
||||
label: '项目素材',
|
||||
sortOrder: 0,
|
||||
collapsed: false,
|
||||
systemDefault: true,
|
||||
},
|
||||
],
|
||||
assets: defaultEditorAssetLibraryAssets,
|
||||
}),
|
||||
);
|
||||
createEditorAssetMock.mockImplementation(async (input) => ({
|
||||
@@ -378,7 +378,9 @@ describe('ImageCanvasEditorView', () => {
|
||||
it('shows the loaded project title and a topbar entry back to projects', async () => {
|
||||
render(<ImageCanvasEditorView />);
|
||||
|
||||
expect(await screen.findByRole('heading', { name: '默认项目' })).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('heading', { name: '默认项目' }),
|
||||
).toBeTruthy();
|
||||
const projectLink = screen.getByRole('link', { name: '返回项目页面' });
|
||||
|
||||
expect(projectLink.getAttribute('href')).toBe('/project');
|
||||
@@ -422,7 +424,9 @@ describe('ImageCanvasEditorView', () => {
|
||||
'新画布项目',
|
||||
);
|
||||
});
|
||||
expect(await screen.findByRole('heading', { name: '新画布项目' })).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('heading', { name: '新画布项目' }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('does not inject built-in mock assets when the persisted library is empty', async () => {
|
||||
@@ -974,7 +978,9 @@ describe('ImageCanvasEditorView', () => {
|
||||
|
||||
expect(openLoginModal).toHaveBeenCalledTimes(1);
|
||||
expect(createEditorAssetMock).not.toHaveBeenCalled();
|
||||
expect(screen.queryByRole('button', { name: '上传失败登录后上传.png' })).toBeNull();
|
||||
expect(
|
||||
screen.queryByRole('button', { name: '上传失败登录后上传.png' }),
|
||||
).toBeNull();
|
||||
|
||||
const resumeUpload = openLoginModal.mock.calls[0]?.[0];
|
||||
expect(typeof resumeUpload).toBe('function');
|
||||
@@ -1027,7 +1033,9 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect(
|
||||
await screen.findByLabelText('素材素材上传进度.png上传进度'),
|
||||
).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: '上传中素材上传进度.png' })).toBeTruthy();
|
||||
expect(
|
||||
screen.getByRole('button', { name: '上传中素材上传进度.png' }),
|
||||
).toBeTruthy();
|
||||
|
||||
deferredAsset.resolve({
|
||||
assetId: 'asset-upload-progress',
|
||||
@@ -1044,9 +1052,7 @@ describe('ImageCanvasEditorView', () => {
|
||||
screen.getByRole('button', { name: '添加素材上传进度.png' }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
expect(
|
||||
screen.queryByLabelText('素材素材上传进度.png上传进度'),
|
||||
).toBeNull();
|
||||
expect(screen.queryByLabelText('素材素材上传进度.png上传进度')).toBeNull();
|
||||
});
|
||||
|
||||
it('opens login when asset creation returns unauthorized during upload', async () => {
|
||||
@@ -1212,6 +1218,69 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect(deleteEditorAssetMock).toHaveBeenCalledWith('asset-b');
|
||||
});
|
||||
|
||||
it('saves a library asset layer right after creating its canvas resource', async () => {
|
||||
const user = userEvent.setup();
|
||||
createEditorProjectResourceMock.mockResolvedValueOnce({
|
||||
resourceId: 'resource-added-asset-a',
|
||||
projectId: 'editor-project-default',
|
||||
imageSrc: 'data:image/png;base64,YQ==',
|
||||
width: 320,
|
||||
height: 240,
|
||||
sourceType: 'uploaded',
|
||||
});
|
||||
loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({
|
||||
projectId: 'editor-project-default',
|
||||
title: '空画布项目',
|
||||
viewport: { x: 0, y: 0, scale: 1 },
|
||||
layers: [],
|
||||
resources: [],
|
||||
updatedAt: '2026-06-12T00:00:00.000Z',
|
||||
});
|
||||
loadEditorAssetLibraryMock.mockResolvedValueOnce({
|
||||
folders: [
|
||||
{
|
||||
folderId: 'project',
|
||||
label: '项目素材',
|
||||
sortOrder: 0,
|
||||
collapsed: false,
|
||||
systemDefault: true,
|
||||
},
|
||||
],
|
||||
assets: [
|
||||
{
|
||||
assetId: 'asset-a',
|
||||
folderId: 'project',
|
||||
label: '账号素材A',
|
||||
imageSrc: 'data:image/png;base64,YQ==',
|
||||
width: 320,
|
||||
height: 240,
|
||||
sourceType: 'uploaded',
|
||||
},
|
||||
],
|
||||
});
|
||||
render(<ImageCanvasEditorView />);
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: '添加账号素材A' }),
|
||||
);
|
||||
|
||||
expect(await screen.findByAltText('画布图片:账号素材A')).toBeTruthy();
|
||||
await waitFor(() => {
|
||||
expect(saveEditorProjectLayoutMock).toHaveBeenCalledWith(
|
||||
'editor-project-default',
|
||||
expect.objectContaining({
|
||||
layers: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
title: '账号素材A',
|
||||
resourceId: 'resource-added-asset-a',
|
||||
sourceAssetId: 'asset-a',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('selects multiple assets with a marquee in asset selection mode', async () => {
|
||||
const user = userEvent.setup();
|
||||
loadEditorAssetLibraryMock.mockResolvedValueOnce({
|
||||
@@ -1601,8 +1670,11 @@ describe('ImageCanvasEditorView', () => {
|
||||
|
||||
const menu = screen.getByRole('menu', { name: '画布右键菜单' });
|
||||
expect(
|
||||
(within(menu).getByRole('menuitem', { name: '粘贴' }) as HTMLButtonElement)
|
||||
.disabled,
|
||||
(
|
||||
within(menu).getByRole('menuitem', {
|
||||
name: '粘贴',
|
||||
}) as HTMLButtonElement
|
||||
).disabled,
|
||||
).toBe(true);
|
||||
expect(within(menu).getByRole('menuitem', { name: '放大' })).toBeTruthy();
|
||||
expect(
|
||||
@@ -1656,11 +1728,15 @@ describe('ImageCanvasEditorView', () => {
|
||||
});
|
||||
const copyPasteMenu = screen.getByRole('menu', { name: '画布右键菜单' });
|
||||
expect(
|
||||
(within(copyPasteMenu).getByRole('menuitem', {
|
||||
name: '粘贴',
|
||||
}) as HTMLButtonElement).disabled,
|
||||
(
|
||||
within(copyPasteMenu).getByRole('menuitem', {
|
||||
name: '粘贴',
|
||||
}) as HTMLButtonElement
|
||||
).disabled,
|
||||
).toBe(false);
|
||||
fireEvent.click(within(copyPasteMenu).getByRole('menuitem', { name: '粘贴' }));
|
||||
fireEvent.click(
|
||||
within(copyPasteMenu).getByRole('menuitem', { name: '粘贴' }),
|
||||
);
|
||||
expect(screen.getAllByAltText(/画布图片:拼图素材/u)).toHaveLength(2);
|
||||
|
||||
fireEvent.contextMenu(
|
||||
@@ -1885,7 +1961,9 @@ describe('ImageCanvasEditorView', () => {
|
||||
).toBeTruthy();
|
||||
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: '缩放至100%' }));
|
||||
expect(screen.getByRole('button', { name: /当前缩放比例 \d+%/u })).toBeTruthy();
|
||||
expect(
|
||||
screen.getByRole('button', { name: /当前缩放比例 \d+%/u }),
|
||||
).toBeTruthy();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '当前缩放比例 100%' }));
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: '缩放至50%' }));
|
||||
@@ -1915,25 +1993,23 @@ describe('ImageCanvasEditorView', () => {
|
||||
});
|
||||
expect(within(settingsPanel).getByText('画布背景')).toBeTruthy();
|
||||
|
||||
fireEvent.click(within(settingsPanel).getByRole('button', { name: '暖灰' }));
|
||||
fireEvent.click(
|
||||
within(settingsPanel).getByRole('button', { name: '暖灰' }),
|
||||
);
|
||||
|
||||
expect((viewport as HTMLElement).style.backgroundColor).toBe(
|
||||
'rgb(243, 240, 234)',
|
||||
);
|
||||
|
||||
fireEvent.change(
|
||||
within(settingsPanel).getByLabelText('自定义画布背景色'),
|
||||
{
|
||||
target: { value: '#ffffff' },
|
||||
},
|
||||
);
|
||||
fireEvent.change(within(settingsPanel).getByLabelText('自定义画布背景色'), {
|
||||
target: { value: '#ffffff' },
|
||||
});
|
||||
expect((viewport as HTMLElement).style.backgroundColor).toBe(
|
||||
'rgb(255, 255, 255)',
|
||||
);
|
||||
|
||||
const hexInput = within(settingsPanel).getByLabelText(
|
||||
'画布背景十六进制颜色',
|
||||
);
|
||||
const hexInput =
|
||||
within(settingsPanel).getByLabelText('画布背景十六进制颜色');
|
||||
fireEvent.change(hexInput, { target: { value: '#abc' } });
|
||||
expect((hexInput as HTMLInputElement).value).toBe('#aabbcc');
|
||||
expect((viewport as HTMLElement).style.backgroundColor).toBe(
|
||||
@@ -1954,9 +2030,7 @@ describe('ImageCanvasEditorView', () => {
|
||||
);
|
||||
|
||||
fireEvent.keyDown(window, { key: 'Escape' });
|
||||
expect(
|
||||
screen.queryByRole('dialog', { name: '画布背景设置' }),
|
||||
).toBeNull();
|
||||
expect(screen.queryByRole('dialog', { name: '画布背景设置' })).toBeNull();
|
||||
|
||||
fireEvent.click(
|
||||
within(panelToolbar).getByRole('button', { name: '切换小地图' }),
|
||||
@@ -2927,7 +3001,9 @@ describe('ImageCanvasEditorView', () => {
|
||||
fireEvent.change(within(characterPanel).getByLabelText('角色设定'), {
|
||||
target: { value: '高个子游侠' },
|
||||
});
|
||||
fireEvent.click(within(characterPanel).getByRole('button', { name: '生成' }));
|
||||
fireEvent.click(
|
||||
within(characterPanel).getByRole('button', { name: '生成' }),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(generateEditorImageMock).toHaveBeenCalledWith(
|
||||
@@ -2937,9 +3013,7 @@ describe('ImageCanvasEditorView', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(
|
||||
generateEditorImageMock.mock.calls[0]?.[0],
|
||||
).not.toEqual(
|
||||
expect(generateEditorImageMock.mock.calls[0]?.[0]).not.toEqual(
|
||||
expect.objectContaining({
|
||||
aspectRatio: expect.any(String),
|
||||
imageSize: expect.any(String),
|
||||
@@ -3019,12 +3093,16 @@ describe('ImageCanvasEditorView', () => {
|
||||
const characterFrame = screen.getByLabelText('角色生成占位图');
|
||||
expect(characterFrame).toBeTruthy();
|
||||
|
||||
dispatchPointerEvent(screen.getByLabelText('图像生成占位图'), 'pointerdown', {
|
||||
button: 0,
|
||||
pointerId: 1702,
|
||||
clientX: 500,
|
||||
clientY: 260,
|
||||
});
|
||||
dispatchPointerEvent(
|
||||
screen.getByLabelText('图像生成占位图'),
|
||||
'pointerdown',
|
||||
{
|
||||
button: 0,
|
||||
pointerId: 1702,
|
||||
clientX: 500,
|
||||
clientY: 260,
|
||||
},
|
||||
);
|
||||
dispatchPointerEvent(screen.getByLabelText('画布工作区'), 'pointermove', {
|
||||
pointerId: 1702,
|
||||
clientX: 650,
|
||||
@@ -3036,9 +3114,7 @@ describe('ImageCanvasEditorView', () => {
|
||||
clientY: 390,
|
||||
});
|
||||
const movedFrame = screen.getByLabelText('图像生成占位图');
|
||||
const movedLeft = Number.parseFloat(
|
||||
(movedFrame as HTMLElement).style.left,
|
||||
);
|
||||
const movedLeft = Number.parseFloat((movedFrame as HTMLElement).style.left);
|
||||
const movedTop = Number.parseFloat((movedFrame as HTMLElement).style.top);
|
||||
expect(movedLeft).toBeGreaterThan(originalLeft);
|
||||
expect(movedTop).toBeGreaterThan(originalTop);
|
||||
@@ -3077,9 +3153,13 @@ describe('ImageCanvasEditorView', () => {
|
||||
.getByAltText(/画布图片:生成图片/)
|
||||
.closest('button') as HTMLElement;
|
||||
const expectedLayerLeft =
|
||||
movedLeft + Number.parseFloat((movedFrame as HTMLElement).style.width) / 2 - 512;
|
||||
movedLeft +
|
||||
Number.parseFloat((movedFrame as HTMLElement).style.width) / 2 -
|
||||
512;
|
||||
const expectedLayerTop =
|
||||
movedTop + Number.parseFloat((movedFrame as HTMLElement).style.height) / 2 - 512;
|
||||
movedTop +
|
||||
Number.parseFloat((movedFrame as HTMLElement).style.height) / 2 -
|
||||
512;
|
||||
expect(Number.parseFloat(generatedLayer.style.left)).toBeCloseTo(
|
||||
expectedLayerLeft,
|
||||
1,
|
||||
@@ -3160,7 +3240,9 @@ describe('ImageCanvasEditorView', () => {
|
||||
iconPanel.querySelector('.image-canvas-editor__icon-spec-card'),
|
||||
).toBeTruthy();
|
||||
|
||||
fireEvent.click(within(iconPanel).getByRole('button', { name: '添加素材描述' }));
|
||||
fireEvent.click(
|
||||
within(iconPanel).getByRole('button', { name: '添加素材描述' }),
|
||||
);
|
||||
|
||||
expect(Number.parseFloat(iconPanel.style.width)).toBeCloseTo(61.2, 1);
|
||||
expect(within(iconPanel).getAllByRole('textbox')).toHaveLength(7);
|
||||
@@ -4358,12 +4440,16 @@ describe('ImageCanvasEditorView', () => {
|
||||
});
|
||||
expect(within(editedMetadataDialog).queryByText('Prompt')).toBeNull();
|
||||
expect(within(editedMetadataDialog).getByText('修改要求')).toBeTruthy();
|
||||
expect(within(editedMetadataDialog).getByText('把画面改成黄昏光线')).toBeTruthy();
|
||||
expect(
|
||||
within(editedMetadataDialog).getByText('把画面改成黄昏光线'),
|
||||
).toBeTruthy();
|
||||
expect(within(editedMetadataDialog).getByText('参考图')).toBeTruthy();
|
||||
expect(
|
||||
within(editedMetadataDialog).getByText(/^生成图片 \d+$/u),
|
||||
).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: /当前缩放比例 \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