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:
saturn
2026-04-03 22:36:41 +08:00
parent 854b932e67
commit 78b93331b4
136 changed files with 3393 additions and 875 deletions

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