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:
143
tests/unit/components/character-section-actions.test.ts
Normal file
143
tests/unit/components/character-section-actions.test.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import * as React from 'react'
|
||||
import { createElement } from 'react'
|
||||
import type { ComponentProps, ReactElement } 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 CharacterSection from '@/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/CharacterSection'
|
||||
|
||||
const useProjectAssetsMock = vi.hoisted(() => vi.fn())
|
||||
const characterCardMock = vi.hoisted(() => vi.fn((_props: unknown) => null))
|
||||
|
||||
vi.mock('@/lib/query/hooks/useProjectAssets', () => ({
|
||||
useProjectAssets: (projectId: string | null) => useProjectAssetsMock(projectId),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/CharacterCard', () => ({
|
||||
__esModule: true,
|
||||
default: (props: unknown) => characterCardMock(props),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/CharacterProfileCard', () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
}))
|
||||
|
||||
vi.mock('@/types/character-profile', () => ({
|
||||
parseProfileData: () => null,
|
||||
}))
|
||||
|
||||
vi.mock('@/components/task/TaskStatusInline', () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/task/presentation', () => ({
|
||||
resolveTaskPresentationState: () => null,
|
||||
}))
|
||||
|
||||
vi.mock('@/components/ui/icons', () => ({
|
||||
AppIcon: (props: { name?: string; className?: string }) =>
|
||||
createElement('span', { 'data-icon': props.name, className: props.className }),
|
||||
}))
|
||||
|
||||
const messages = {
|
||||
assets: {
|
||||
stage: {
|
||||
characterAssets: '角色资产',
|
||||
counts: '{characterCount} 个角色,{appearanceCount} 个形象',
|
||||
pendingProfilesBanner: '待确认角色',
|
||||
pendingProfilesHint: '确认角色设定',
|
||||
confirmAll: '全部确认',
|
||||
},
|
||||
character: {
|
||||
add: '新建角色',
|
||||
assetCount: '{count} 个形象',
|
||||
copyFromGlobal: '从资产中心导入',
|
||||
delete: '删除角色',
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
function renderWithIntl(node: ReactElement) {
|
||||
const providerProps: ComponentProps<typeof NextIntlClientProvider> = {
|
||||
locale: 'zh',
|
||||
messages: messages as unknown as AbstractIntlMessages,
|
||||
timeZone: 'Asia/Shanghai',
|
||||
children: node,
|
||||
}
|
||||
|
||||
return renderToStaticMarkup(
|
||||
createElement(NextIntlClientProvider, providerProps),
|
||||
)
|
||||
}
|
||||
|
||||
describe('CharacterSection actions', () => {
|
||||
it('renders import and delete actions stacked vertically with the import icon', () => {
|
||||
Reflect.set(globalThis, 'React', React)
|
||||
useProjectAssetsMock.mockReturnValue({
|
||||
data: {
|
||||
characters: [
|
||||
{
|
||||
id: 'character-1',
|
||||
name: '西装男',
|
||||
introduction: null,
|
||||
appearances: [
|
||||
{
|
||||
id: 'appearance-1',
|
||||
appearanceIndex: 0,
|
||||
changeReason: '初始形象',
|
||||
imageUrl: null,
|
||||
imageUrls: [],
|
||||
selectedIndex: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const html = renderWithIntl(
|
||||
createElement(CharacterSection, {
|
||||
projectId: 'project-1',
|
||||
activeTaskKeys: new Set<string>(),
|
||||
onClearTaskKey: () => undefined,
|
||||
onRegisterTransientTaskKey: () => undefined,
|
||||
isAnalyzingAssets: false,
|
||||
onAddCharacter: () => undefined,
|
||||
onDeleteCharacter: () => undefined,
|
||||
onDeleteAppearance: () => undefined,
|
||||
onEditAppearance: () => undefined,
|
||||
handleGenerateImage: async () => undefined,
|
||||
onSelectImage: () => undefined,
|
||||
onConfirmSelection: () => undefined,
|
||||
onRegenerateSingle: async () => undefined,
|
||||
onRegenerateGroup: async () => undefined,
|
||||
onUndo: () => undefined,
|
||||
onImageClick: () => undefined,
|
||||
onImageEdit: () => undefined,
|
||||
onVoiceChange: () => undefined,
|
||||
onVoiceDesign: () => undefined,
|
||||
onVoiceSelectFromHub: () => undefined,
|
||||
onCopyFromGlobal: () => undefined,
|
||||
getAppearances: (character) => character.appearances,
|
||||
unconfirmedCharacters: [],
|
||||
isConfirmingCharacter: () => false,
|
||||
deletingCharacterId: null,
|
||||
batchConfirming: false,
|
||||
batchConfirmingState: null,
|
||||
onBatchConfirm: () => undefined,
|
||||
onEditProfile: () => undefined,
|
||||
onConfirmProfile: () => undefined,
|
||||
onUseExistingProfile: () => undefined,
|
||||
onDeleteProfile: () => undefined,
|
||||
}),
|
||||
)
|
||||
|
||||
expect(html).toContain('从资产中心导入')
|
||||
expect(html).toContain('删除角色')
|
||||
expect(html).toContain('data-icon="arrowDownCircle"')
|
||||
expect(html).toContain('flex flex-col items-end gap-1.5')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user