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:
243
tests/unit/components/asset-hub-card-aspect-ratio.test.ts
Normal file
243
tests/unit/components/asset-hub-card-aspect-ratio.test.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
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'
|
||||
|
||||
const idleMutation = {
|
||||
isPending: false,
|
||||
mutate: vi.fn(),
|
||||
}
|
||||
|
||||
vi.mock('@/lib/query/mutations', () => ({
|
||||
useGenerateCharacterImage: () => idleMutation,
|
||||
useSelectCharacterImage: () => idleMutation,
|
||||
useUndoCharacterImage: () => idleMutation,
|
||||
useUploadCharacterImage: () => idleMutation,
|
||||
useDeleteCharacter: () => idleMutation,
|
||||
useDeleteCharacterAppearance: () => idleMutation,
|
||||
useUploadCharacterVoice: () => idleMutation,
|
||||
useGenerateLocationImage: () => idleMutation,
|
||||
useSelectLocationImage: () => idleMutation,
|
||||
useUndoLocationImage: () => idleMutation,
|
||||
useUploadLocationImage: () => idleMutation,
|
||||
useDeleteLocation: () => idleMutation,
|
||||
}))
|
||||
|
||||
vi.mock('@/components/ui/icons', () => ({
|
||||
AppIcon: (props: { className?: string; name?: string }) =>
|
||||
createElement('span', { className: props.className, 'data-icon': props.name }),
|
||||
}))
|
||||
|
||||
vi.mock('@/components/task/TaskStatusOverlay', () => ({
|
||||
default: () => createElement('div', null, 'overlay'),
|
||||
}))
|
||||
|
||||
vi.mock('@/components/task/TaskStatusInline', () => ({
|
||||
default: () => createElement('span', null, 'inline'),
|
||||
}))
|
||||
|
||||
vi.mock('@/components/media/MediaImageWithLoading', () => ({
|
||||
MediaImageWithLoading: (props: { containerClassName?: string; className?: string }) =>
|
||||
createElement('div', {
|
||||
className: [props.containerClassName, props.className].filter(Boolean).join(' '),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/components/image-generation/ImageGenerationInlineCountButton', () => ({
|
||||
default: () => createElement('button', null, 'count'),
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/task/presentation', () => ({
|
||||
resolveTaskPresentationState: () => 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('@/app/[locale]/workspace/asset-hub/components/VoiceSettings', () => ({
|
||||
default: () => createElement('div', null, 'voice-settings'),
|
||||
}))
|
||||
|
||||
const messages = {
|
||||
assetHub: {
|
||||
generateFailed: '生成失败',
|
||||
selectFailed: '选择失败',
|
||||
uploadFailed: '上传失败',
|
||||
confirmDeleteLocation: '确认删除场景',
|
||||
confirmDeleteProp: '确认删除道具',
|
||||
confirmDeleteCharacter: '确认删除角色',
|
||||
cancel: '取消',
|
||||
delete: '删除',
|
||||
propLabel: '道具',
|
||||
locationLabel: '场景',
|
||||
},
|
||||
assets: {
|
||||
image: {
|
||||
generateCountPrefix: '生成',
|
||||
generateCountSuffix: '张',
|
||||
generating: '生成中',
|
||||
generatingPlaceholder: '正在生成',
|
||||
regenerateStuck: '重新生成',
|
||||
regenCountPrefix: '重生成',
|
||||
undo: '撤回',
|
||||
upload: '上传',
|
||||
uploadReplace: '替换',
|
||||
edit: '编辑',
|
||||
selectCount: '选择数量',
|
||||
confirmOption: '确认选择',
|
||||
optionNumber: '方案 {number}',
|
||||
},
|
||||
common: {
|
||||
generateFailed: '生成失败',
|
||||
},
|
||||
location: {
|
||||
regenerateImage: '重生成场景',
|
||||
edit: '编辑场景',
|
||||
delete: '删除场景',
|
||||
},
|
||||
prop: {
|
||||
regenerateImage: '重生成道具',
|
||||
edit: '编辑道具',
|
||||
delete: '删除道具',
|
||||
},
|
||||
character: {
|
||||
deleteWhole: '删除整个角色',
|
||||
primary: '主形象',
|
||||
secondary: '子形象',
|
||||
delete: '删除角色',
|
||||
deleteOptions: '删除选项',
|
||||
},
|
||||
video: {
|
||||
panelCard: {
|
||||
editPrompt: '编辑',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
const TestIntlProvider = NextIntlClientProvider as React.ComponentType<{
|
||||
locale: string
|
||||
messages: AbstractIntlMessages
|
||||
timeZone: string
|
||||
children?: React.ReactNode
|
||||
}>
|
||||
|
||||
function renderWithIntl(node: React.ReactElement) {
|
||||
return renderToStaticMarkup(
|
||||
createElement(
|
||||
TestIntlProvider,
|
||||
{
|
||||
locale: 'zh',
|
||||
messages: messages as unknown as AbstractIntlMessages,
|
||||
timeZone: 'Asia/Shanghai',
|
||||
},
|
||||
node,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
describe('asset hub card aspect ratio', () => {
|
||||
it('keeps prop cards at the same 3:2 ratio as character assets while generation is running', async () => {
|
||||
Reflect.set(globalThis, 'React', React)
|
||||
const { default: LocationCard } = await import('@/app/[locale]/workspace/asset-hub/components/LocationCard')
|
||||
const html = renderWithIntl(
|
||||
createElement(LocationCard, {
|
||||
location: {
|
||||
id: 'prop-1',
|
||||
name: '鼠标',
|
||||
summary: '电脑鼠标',
|
||||
folderId: null,
|
||||
images: [
|
||||
{
|
||||
id: 'prop-image-1',
|
||||
imageIndex: 0,
|
||||
description: null,
|
||||
imageUrl: null,
|
||||
previousImageUrl: null,
|
||||
isSelected: false,
|
||||
imageTaskRunning: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
assetType: 'prop',
|
||||
}),
|
||||
)
|
||||
|
||||
expect(html).toContain('aspect-[3/2]')
|
||||
expect(html).toContain('data-icon="image"')
|
||||
expect(html).not.toContain('min-h-[100px]')
|
||||
})
|
||||
|
||||
it('keeps location cards square while generation is running', async () => {
|
||||
Reflect.set(globalThis, 'React', React)
|
||||
const { default: LocationCard } = await import('@/app/[locale]/workspace/asset-hub/components/LocationCard')
|
||||
const html = renderWithIntl(
|
||||
createElement(LocationCard, {
|
||||
location: {
|
||||
id: 'location-1',
|
||||
name: '餐厅',
|
||||
summary: '极简餐厅',
|
||||
folderId: null,
|
||||
images: [
|
||||
{
|
||||
id: 'location-image-1',
|
||||
imageIndex: 0,
|
||||
description: null,
|
||||
imageUrl: null,
|
||||
previousImageUrl: null,
|
||||
isSelected: false,
|
||||
imageTaskRunning: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
assetType: 'location',
|
||||
}),
|
||||
)
|
||||
|
||||
expect(html).toContain('aspect-square')
|
||||
expect(html).toContain('data-icon="image"')
|
||||
expect(html).not.toContain('min-h-[100px]')
|
||||
})
|
||||
|
||||
it('keeps character cards at the fixed 3:2 ratio while generation is running', async () => {
|
||||
Reflect.set(globalThis, 'React', React)
|
||||
const { CharacterCard } = await import('@/app/[locale]/workspace/asset-hub/components/CharacterCard')
|
||||
const html = renderWithIntl(
|
||||
createElement(CharacterCard, {
|
||||
character: {
|
||||
id: 'character-1',
|
||||
name: '沈烬',
|
||||
folderId: null,
|
||||
customVoiceUrl: null,
|
||||
appearances: [
|
||||
{
|
||||
id: 'appearance-1',
|
||||
appearanceIndex: 0,
|
||||
changeReason: '默认形象',
|
||||
description: null,
|
||||
imageUrl: null,
|
||||
imageUrls: [],
|
||||
selectedIndex: null,
|
||||
previousImageUrl: null,
|
||||
previousImageUrls: [],
|
||||
imageTaskRunning: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(html).toContain('aspect-[3/2]')
|
||||
expect(html).not.toContain('min-h-[100px]')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user