144 lines
4.6 KiB
TypeScript
144 lines
4.6 KiB
TypeScript
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')
|
|
})
|
|
})
|