Files
Genarrative/src/components/common/PlatformMediaFrame.test.tsx
T
kdletters 1ad25e30f8 收口前端平台组件库能力
新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件
迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome
补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
2026-06-10 10:24:18 +08:00

296 lines
8.3 KiB
TypeScript

/* @vitest-environment jsdom */
import { render, screen } from '@testing-library/react';
import { createRef, type ImgHTMLAttributes } from 'react';
import { expect, test, vi } from 'vitest';
import { PlatformMediaFrame } from './PlatformMediaFrame';
vi.mock('../ResolvedAssetImage', () => ({
ResolvedAssetImage: ({
src,
fallbackSrc,
alt,
className,
refreshKey,
...imageProps
}: {
src?: string;
fallbackSrc?: string;
alt: string;
className?: string;
refreshKey?: string | number | null;
} & ImgHTMLAttributes<HTMLImageElement>) => (
<img
{...imageProps}
src={src ?? fallbackSrc}
data-fallback-src={fallbackSrc}
data-refresh-key={refreshKey ?? undefined}
alt={alt}
className={className}
/>
),
}));
test('renders warm media frame with image and landscape aspect', () => {
render(
<PlatformMediaFrame
src="/scene.png"
alt="潮灯居"
fallbackLabel="场景"
aspect="landscape"
/>,
);
const image = screen.getByRole('img', { name: '潮灯居' });
const frame = image.closest('div');
expect(frame?.className).toContain('platform-media-frame');
expect(frame?.className).toContain('aspect-[16/9]');
expect(frame?.className).toContain(
'border-[var(--platform-subpanel-border)]',
);
expect(frame?.className).toContain('radial-gradient');
expect(image.className).toContain('object-cover');
});
test('renders fallback label when no image source is available', () => {
render(
<PlatformMediaFrame
alt="角色"
fallbackLabel="角色"
aspect="square"
surface="plain"
/>,
);
const fallback = screen.getByText('角色');
const frame = fallback.closest('div.relative');
expect(frame?.className).toContain('aspect-square');
expect(frame?.className).toContain('bg-[var(--platform-subpanel-fill)]');
expect(fallback.className).toContain('tracking-[0.18em]');
});
test('supports custom fallback content', () => {
render(
<PlatformMediaFrame
alt="封面图"
fallbackLabel="封面图占位"
fallbackContent={<span data-testid="fallback-icon">图标</span>}
aspect="standard"
surface="plain"
fallbackShellClassName="bg-rainbow"
fallbackClassName="tracking-normal"
/>,
);
const fallbackIcon = screen.getByTestId('fallback-icon');
const fallback = fallbackIcon.closest('div');
const frame = fallbackIcon.closest('div.relative');
expect(fallbackIcon.textContent).toBe('图标');
expect(screen.queryByText('封面图占位')).toBeNull();
expect(fallback?.className).toContain('bg-rainbow');
expect(fallback?.className).toContain('tracking-normal');
expect(frame?.className).toContain('aspect-[4/3]');
});
test('supports soft media frame surface', () => {
render(
<PlatformMediaFrame
src="/template-preview.webp"
alt="创意模板"
fallbackLabel="创意模板"
aspect="landscape"
surface="soft"
/>,
);
const image = screen.getByRole('img', { name: '创意模板' });
const frame = image.closest('div');
expect(frame?.className).toContain('aspect-[16/9]');
expect(frame?.className).toContain(
'border-[var(--platform-subpanel-border)]',
);
expect(frame?.className).toContain('bg-white/68');
});
test('supports bright media frame surface', () => {
render(
<PlatformMediaFrame
src="/match3d/item.png"
alt="物品素材"
fallbackLabel="物品素材"
aspect="square"
surface="bright"
/>,
);
const image = screen.getByRole('img', { name: '物品素材' });
const frame = image.closest('div');
expect(frame?.className).toContain('aspect-square');
expect(frame?.className).toContain(
'border-[var(--platform-subpanel-border)]',
);
expect(frame?.className).toContain('bg-white/82');
});
test('passes div attributes to the media frame container', () => {
render(
<PlatformMediaFrame
src="/match3d/item.png"
alt="物品素材"
fallbackLabel="物品素材"
surface="bright"
data-testid="match3d-preview-frame"
aria-label="物品素材预览"
/>,
);
const frame = screen.getByTestId('match3d-preview-frame');
expect(frame.getAttribute('aria-label')).toBe('物品素材预览');
expect(frame.className).toContain('bg-white/82');
});
test('supports none surface for nested interactive shells', () => {
render(
<PlatformMediaFrame
src="/match3d/thumb.png"
alt="缩略图"
fallbackLabel="缩略图"
aspect="square"
surface="none"
className="rounded-[0.65rem]"
/>,
);
const image = screen.getByRole('img', { name: '缩略图' });
const frame = image.closest('div');
expect(frame?.className).toContain('aspect-square');
expect(frame?.className).toContain('rounded-[0.65rem]');
expect(frame?.className).not.toContain('border-[');
expect(frame?.className).not.toContain('bg-white');
});
test('supports editor dark surface, fallback source and overlay', () => {
render(
<PlatformMediaFrame
src=""
fallbackSrc="/fallback.png"
alt="第1幕"
fallbackLabel="第1幕"
surface="editorDark"
loading="lazy"
overlayInteractive
previewOverlay={<span>覆盖层</span>}
/>,
);
const image = screen.getByRole('img', { name: '第1幕' });
const frame = image.closest('div');
const overlay = screen.getByText('覆盖层').parentElement;
expect(image.getAttribute('src')).toBe('/fallback.png');
expect(frame?.className).toContain('border-white/10');
expect(frame?.className).toContain('rgba(19,24,39,0.95)');
expect(overlay?.className).toContain('pointer-events-auto');
});
test('supports standard aspect, bare surface and refresh key', () => {
render(
<PlatformMediaFrame
src="/puzzle/level.png"
alt="雨夜猫街"
fallbackLabel="暂无正式图"
aspect="standard"
surface="bare"
refreshKey="session-1:level-1"
/>,
);
const image = screen.getByRole('img', { name: '雨夜猫街' });
const frame = image.closest('div');
expect(frame?.className).toContain('aspect-[4/3]');
expect(frame?.className).toContain('bg-[var(--platform-subpanel-fill)]');
expect(frame?.className).not.toContain('border');
expect(image.getAttribute('data-refresh-key')).toBe('session-1:level-1');
});
test('supports portrait aspect for vertical preview assets', () => {
render(
<PlatformMediaFrame
src="/puzzle-clear/board-background.png"
alt="场地底图"
fallbackLabel="底图"
aspect="portrait"
surface="bare"
className="rounded-none bg-white/80"
/>,
);
const image = screen.getByRole('img', { name: '场地底图' });
const frame = image.closest('div');
expect(frame?.className).toContain('aspect-[9/16]');
expect(frame?.className).toContain('bg-white/80');
expect(frame?.className).toContain('rounded-none');
});
test('supports wide aspect for broad preview assets', () => {
render(
<PlatformMediaFrame
src="/big-fish/asset-preview.png"
alt="大鱼素材候选"
fallbackLabel="AI 资产候选预览"
aspect="wide"
surface="plain"
/>,
);
const image = screen.getByRole('img', { name: '大鱼素材候选' });
const frame = image.closest('div');
expect(frame?.className).toContain('aspect-[9/5]');
expect(frame?.className).toContain(
'border-[var(--platform-subpanel-border)]',
);
});
test('supports auto aspect, image props and container refs', () => {
const frameRef = createRef<HTMLDivElement>();
render(
<PlatformMediaFrame
ref={frameRef}
src="/cover-upload.png"
alt="封面裁剪预览"
fallbackLabel="封面"
aspect="auto"
surface="none"
imageClassName="absolute max-w-none object-fill"
imageProps={{
draggable: false,
style: { left: '-10%', width: '120%' },
}}
data-testid="cover-crop-frame"
/>,
);
const frame = screen.getByTestId('cover-crop-frame');
const image = screen.getByRole('img', { name: '封面裁剪预览' });
expect(frameRef.current).toBe(frame);
expect(frame.className).toContain('platform-media-frame');
expect(frame.className).not.toContain('aspect-[');
expect(frame.className).not.toContain('aspect-square');
expect(image.getAttribute('draggable')).toBe('false');
expect(image.getAttribute('style')).toContain('left: -10%');
expect(image.className).toContain('absolute max-w-none object-fill');
});