188 lines
6.1 KiB
TypeScript
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')
|
|
})
|
|
})
|