Fix prop confirmation bug, add Wan 2.7 model, refine multiple UI details, improve prop generation quality and aspect ratio, remove text overlays from Asset Center created images, and optimize prop filtering logic
This commit is contained in:
187
tests/unit/components/location-card-ai-edit.test.ts
Normal file
187
tests/unit/components/location-card-ai-edit.test.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
import * as React from 'react'
|
||||
import { createElement } from 'react'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { NextIntlClientProvider } from 'next-intl'
|
||||
import type { AbstractIntlMessages } from 'next-intl'
|
||||
import { AI_EDIT_BUTTON_CLASS } from '@/components/ui/ai-edit-style'
|
||||
|
||||
const locationImageListMock = vi.hoisted(() => vi.fn((props: { overlayActions?: React.ReactNode }) => createElement('div', null, props.overlayActions ?? null)))
|
||||
const uploadMutationMock = vi.hoisted(() => ({
|
||||
isPending: false,
|
||||
mutate: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/query/mutations', () => ({
|
||||
useUploadProjectLocationImage: () => uploadMutationMock,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/location-card/LocationCardHeader', () => ({
|
||||
default: () => createElement('div', null),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/location-card/LocationCardActions', () => ({
|
||||
default: () => createElement('div', null),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/location-card/LocationImageList', () => ({
|
||||
default: locationImageListMock,
|
||||
}))
|
||||
|
||||
vi.mock('@/components/ui/icons', () => ({
|
||||
AppIcon: () => createElement('span', null),
|
||||
}))
|
||||
|
||||
vi.mock('@/components/ui/icons/AISparklesIcon', () => ({
|
||||
default: (props: { className?: string }) => createElement('svg', { className: props.className, 'data-icon': 'ai-sparkles' }),
|
||||
}))
|
||||
|
||||
vi.mock('@/components/task/TaskStatusInline', () => ({
|
||||
default: () => createElement('span', null),
|
||||
}))
|
||||
|
||||
vi.mock('@/components/image-generation/ImageGenerationInlineCountButton', () => ({
|
||||
default: () => createElement('button', null),
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/image-generation/use-image-generation-count', () => ({
|
||||
useImageGenerationCount: () => ({
|
||||
count: 1,
|
||||
setCount: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/image-generation/count', () => ({
|
||||
getImageGenerationCountOptions: () => [{ value: 1, label: '1' }],
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/task/presentation', () => ({
|
||||
resolveTaskPresentationState: () => null,
|
||||
}))
|
||||
|
||||
const messages = {
|
||||
assets: {
|
||||
image: {
|
||||
upload: '上传图片',
|
||||
uploadReplace: '上传替换图片',
|
||||
edit: '编辑图片',
|
||||
undo: '撤回',
|
||||
regenerateStuck: '重新生成',
|
||||
},
|
||||
location: {
|
||||
regenerateImage: '重新生成场景',
|
||||
edit: '编辑场景',
|
||||
delete: '删除场景',
|
||||
},
|
||||
prop: {
|
||||
regenerateImage: '重新生成道具',
|
||||
edit: '编辑道具',
|
||||
delete: '删除道具',
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
const TestIntlProvider = NextIntlClientProvider as React.ComponentType<{
|
||||
locale: string
|
||||
messages: AbstractIntlMessages
|
||||
timeZone: string
|
||||
children?: React.ReactNode
|
||||
}>
|
||||
|
||||
describe('LocationCard AI edit button', () => {
|
||||
it('uses the shared AI edit button style in single-image mode', async () => {
|
||||
locationImageListMock.mockClear()
|
||||
Reflect.set(globalThis, 'React', React)
|
||||
const { default: LocationCard } = await import('@/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/LocationCard')
|
||||
const html = renderToStaticMarkup(
|
||||
createElement(
|
||||
TestIntlProvider,
|
||||
{
|
||||
locale: 'zh',
|
||||
messages: messages as unknown as AbstractIntlMessages,
|
||||
timeZone: 'Asia/Shanghai',
|
||||
},
|
||||
createElement(LocationCard, {
|
||||
location: {
|
||||
id: 'prop-1',
|
||||
name: '银质餐具',
|
||||
summary: '银质西式餐具套装',
|
||||
selectedImageId: 'prop-image-1',
|
||||
images: [
|
||||
{
|
||||
id: 'prop-image-1',
|
||||
imageIndex: 0,
|
||||
description: '银质餐具套装,包含刀叉与汤匙,金属光泽冷白',
|
||||
imageUrl: 'https://example.com/prop.png',
|
||||
previousImageUrl: null,
|
||||
previousDescription: null,
|
||||
isSelected: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
assetType: 'prop',
|
||||
onEdit: () => undefined,
|
||||
onDelete: () => undefined,
|
||||
onRegenerate: () => undefined,
|
||||
onGenerate: () => undefined,
|
||||
onImageClick: () => undefined,
|
||||
onImageEdit: () => undefined,
|
||||
projectId: 'project-1',
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
expect(html).toContain('data-icon=\"ai-sparkles\"')
|
||||
for (const token of AI_EDIT_BUTTON_CLASS.split(' ')) {
|
||||
expect(html).toContain(token)
|
||||
}
|
||||
const firstCall = locationImageListMock.mock.calls[0]?.[0] as { aspectClassName?: string } | undefined
|
||||
expect(firstCall?.aspectClassName).toBe('aspect-[3/2]')
|
||||
})
|
||||
|
||||
it('passes a square image slot to project location cards', async () => {
|
||||
locationImageListMock.mockClear()
|
||||
Reflect.set(globalThis, 'React', React)
|
||||
const { default: LocationCard } = await import('@/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/LocationCard')
|
||||
renderToStaticMarkup(
|
||||
createElement(
|
||||
TestIntlProvider,
|
||||
{
|
||||
locale: 'zh',
|
||||
messages: messages as unknown as AbstractIntlMessages,
|
||||
timeZone: 'Asia/Shanghai',
|
||||
},
|
||||
createElement(LocationCard, {
|
||||
location: {
|
||||
id: 'location-1',
|
||||
name: '餐厅',
|
||||
summary: '极简餐厅',
|
||||
selectedImageId: 'location-image-1',
|
||||
images: [
|
||||
{
|
||||
id: 'location-image-1',
|
||||
imageIndex: 0,
|
||||
description: '极简餐厅室内空间',
|
||||
imageUrl: 'https://example.com/location.png',
|
||||
previousImageUrl: null,
|
||||
previousDescription: null,
|
||||
isSelected: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
assetType: 'location',
|
||||
onEdit: () => undefined,
|
||||
onDelete: () => undefined,
|
||||
onRegenerate: () => undefined,
|
||||
onGenerate: () => undefined,
|
||||
onImageClick: () => undefined,
|
||||
onImageEdit: () => undefined,
|
||||
projectId: 'project-1',
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
const firstCall = locationImageListMock.mock.calls[0]?.[0] as { aspectClassName?: string } | undefined
|
||||
expect(firstCall?.aspectClassName).toBe('aspect-square')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user