Files
waooplus/tests/unit/components/location-card-ai-edit.test.ts

188 lines
6.1 KiB
TypeScript

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')
})
})