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 useQueryMock = vi.hoisted(() => vi.fn()) vi.mock('@tanstack/react-query', () => ({ useQuery: (options: unknown) => useQueryMock(options), })) vi.mock('@/components/ui/ImagePreviewModal', () => ({ __esModule: true, default: () => null, })) vi.mock('@/components/task/TaskStatusInline', () => ({ __esModule: true, default: () => null, })) vi.mock('@/lib/task/presentation', () => ({ resolveTaskPresentationState: () => null, })) vi.mock('@/components/media/MediaImageWithLoading', () => ({ MediaImageWithLoading: (props: { src: string; alt: string; className?: string; containerClassName?: string }) => createElement('img', { src: props.src, alt: props.alt, className: [props.className, props.containerClassName].filter(Boolean).join(' '), }), })) vi.mock('@/components/ui/icons', () => ({ AppIcon: (props: { name?: string; className?: string }) => createElement('span', { 'data-icon': props.name, className: props.className }), })) const messages = { assetPicker: { selectCharacter: '从资产中心选择角色', selectLocation: '从资产中心选择场景', selectProp: '从资产中心选择道具', selectVoice: '从资产中心选择音色', searchPlaceholder: '搜索资产名称或文件夹...', noAssets: '资产中心暂无资产', createInAssetHub: '请先在资产中心创建角色/场景/音色', noSearchResults: '未找到匹配的资产', appearances: '个形象', images: '张图片', cancel: '取消', confirmCopy: '确认导入', }, } as const const TestIntlProvider = NextIntlClientProvider as React.ComponentType<{ locale: string messages: AbstractIntlMessages timeZone: string children?: React.ReactNode }> describe('GlobalAssetPicker preview mapping', () => { it('renders the real character preview image at 3:2 without the appearance count line', async () => { Reflect.set(globalThis, 'React', React) useQueryMock.mockReset() useQueryMock.mockImplementation((options: { enabled?: boolean }) => ({ data: options.enabled ? [{ id: 'character-1', kind: 'character', family: 'visual', scope: 'global', name: '西装男', folderId: null, capabilities: { canGenerate: true, canSelectRender: true, canRevertRender: true, canModifyRender: true, canUploadRender: true, canBindVoice: true, canCopyFromGlobal: false, }, taskRefs: [], taskState: { isRunning: false, lastError: null }, introduction: null, profileData: null, profileConfirmed: null, profileTaskRefs: [], profileTaskState: { isRunning: false, lastError: null }, voice: { voiceType: null, voiceId: null, customVoiceUrl: null, media: null, }, variants: [{ id: 'variant-1', index: 0, label: '默认形象', description: '黑西装', selectionState: { selectedRenderIndex: 0 }, taskRefs: [], taskState: { isRunning: false, lastError: null }, renders: [{ id: 'render-1', index: 0, imageUrl: 'https://example.com/character.png', media: null, isSelected: true, previousImageUrl: null, previousMedia: null, taskRefs: [], taskState: { isRunning: false, lastError: null }, }], }], }] : [], isFetching: false, refetch: vi.fn(), })) const { default: GlobalAssetPicker } = await import('@/components/shared/assets/GlobalAssetPicker') const html = renderToStaticMarkup( createElement( TestIntlProvider, { locale: 'zh', messages: messages as unknown as AbstractIntlMessages, timeZone: 'Asia/Shanghai', }, createElement(GlobalAssetPicker, { isOpen: true, onClose: () => undefined, onSelect: () => undefined, type: 'character', }), ), ) expect(html).toContain('src="https://example.com/character.png"') expect(html).toContain('aspect-[3/2]') expect(html).toContain('object-contain') expect(html).not.toContain('data-icon="userAlt"') expect(html).not.toContain('border-b') expect(html).not.toContain('个形象') }) })