7f8400fd3a
将 PlatformAsyncStatePanel 扩展到公共素材选择网格 将 PlatformAsyncStatePanel 扩展到视觉小说存档面板与账号安全子区块 将兑换码弹窗提交动作改为使用标准 profile modal footer 补充对应测试并更新 PlatformUiKit 收口计划与共享决策记录
265 lines
7.3 KiB
TypeScript
265 lines
7.3 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { fireEvent, render, screen } from '@testing-library/react';
|
|
import { expect, test, vi } from 'vitest';
|
|
|
|
import {
|
|
PlatformAssetPickerCard,
|
|
PlatformAssetPickerGrid,
|
|
} from './PlatformAssetPickerCard';
|
|
|
|
vi.mock('../ResolvedAssetImage', () => ({
|
|
ResolvedAssetImage: ({
|
|
src,
|
|
alt,
|
|
className,
|
|
}: {
|
|
src?: string | null;
|
|
alt?: string;
|
|
className?: string;
|
|
}) => <img src={src ?? ''} alt={alt} className={className} />,
|
|
}));
|
|
|
|
test('renders historical asset thumbnail with title and subtitle', () => {
|
|
render(
|
|
<PlatformAssetPickerCard
|
|
imageSrc="/history/a.png"
|
|
imageAlt="历史图片"
|
|
assetTitle="封面图"
|
|
subtitle="2026-06-09 10:00"
|
|
aria-label="选择封面图"
|
|
/>,
|
|
);
|
|
|
|
const button = screen.getByRole('button', { name: '选择封面图' });
|
|
const image = screen.getByRole('img', { name: '历史图片' });
|
|
|
|
expect(button.className).toContain(
|
|
'border-[var(--platform-subpanel-border)]',
|
|
);
|
|
expect(button.className).toContain('hover:border-amber-300/70');
|
|
expect(image.getAttribute('src')).toBe('/history/a.png');
|
|
expect(screen.getByText('封面图')).toBeTruthy();
|
|
expect(screen.getByText('2026-06-09 10:00')).toBeTruthy();
|
|
});
|
|
|
|
test('keeps disabled picker card inert', () => {
|
|
const onClick = vi.fn();
|
|
|
|
render(
|
|
<PlatformAssetPickerCard
|
|
imageSrc="/history/a.png"
|
|
imageAlt=""
|
|
assetTitle="历史素材"
|
|
disabled
|
|
onClick={onClick}
|
|
/>,
|
|
);
|
|
|
|
const button = screen.getByRole('button', { name: '历史素材' });
|
|
fireEvent.click(button);
|
|
|
|
expect(button).toHaveProperty('disabled', true);
|
|
expect(button.className).toContain('cursor-not-allowed');
|
|
expect(onClick).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('supports local radius and body classes for dense pickers', () => {
|
|
render(
|
|
<PlatformAssetPickerCard
|
|
imageSrc="/history/a.png"
|
|
imageAlt="历史图片"
|
|
subtitle="刚刚"
|
|
cardRadiusClassName="rounded-[1.25rem]"
|
|
bodyClassName="px-4 py-3"
|
|
aria-label="选择历史图片"
|
|
/>,
|
|
);
|
|
|
|
const button = screen.getByRole('button', { name: '选择历史图片' });
|
|
|
|
expect(button.className).toContain('rounded-[1.25rem]');
|
|
expect(screen.getByText('刚刚').parentElement?.className).toContain('px-4');
|
|
});
|
|
|
|
test('supports local image shell classes for landscape assets', () => {
|
|
render(
|
|
<PlatformAssetPickerCard
|
|
imageSrc="/history/scene.png"
|
|
imageAlt="场景图"
|
|
imageShellClassName="aspect-[16/9]"
|
|
aria-label="选择场景图"
|
|
/>,
|
|
);
|
|
|
|
expect(
|
|
screen.getByRole('img', { name: '场景图' }).parentElement?.className,
|
|
).toContain('aspect-[16/9]');
|
|
});
|
|
|
|
test('renders selected picker cards with shared selected chrome', () => {
|
|
render(
|
|
<PlatformAssetPickerCard
|
|
imageSrc="/history/a.png"
|
|
imageAlt="历史图片"
|
|
assetTitle="已选择素材"
|
|
aria-label="选择历史图片"
|
|
selected
|
|
/>,
|
|
);
|
|
|
|
const button = screen.getByRole('button', { name: '选择历史图片' });
|
|
|
|
expect(button.className).toContain('ring-2');
|
|
expect(button.className).toContain('border-[var(--platform-warm-border)]');
|
|
});
|
|
|
|
test('renders shared loading and empty states for asset grids', () => {
|
|
const { rerender } = render(
|
|
<PlatformAssetPickerGrid
|
|
items={[]}
|
|
isLoading
|
|
loadingLabel="读取中..."
|
|
emptyLabel="暂无历史素材"
|
|
getKey={(item: { id: string }) => item.id}
|
|
getImageSrc={(item) => item.id}
|
|
getImageAlt={() => ''}
|
|
onSelect={() => {}}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('读取中...').className).toContain('border-dashed');
|
|
|
|
rerender(
|
|
<PlatformAssetPickerGrid
|
|
items={[]}
|
|
loadingLabel="读取中..."
|
|
emptyLabel="暂无历史素材"
|
|
getKey={(item: { id: string }) => item.id}
|
|
getImageSrc={(item) => item.id}
|
|
getImageAlt={() => ''}
|
|
onSelect={() => {}}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('暂无历史素材').className).toContain('border-dashed');
|
|
});
|
|
|
|
test('renders selectable asset grid cards with shared error chrome', () => {
|
|
const onSelect = vi.fn();
|
|
|
|
render(
|
|
<PlatformAssetPickerGrid
|
|
items={[
|
|
{
|
|
id: 'asset-1',
|
|
imageSrc: '/history/a.png',
|
|
title: '历史素材',
|
|
createdAt: '2026-06-09',
|
|
},
|
|
]}
|
|
error="历史素材读取失败。"
|
|
loadingLabel="读取中..."
|
|
emptyLabel="暂无历史素材"
|
|
getKey={(item) => item.id}
|
|
getImageSrc={(item) => item.imageSrc}
|
|
getImageAlt={() => '历史素材'}
|
|
getTitle={(item) => item.title}
|
|
getSubtitle={(item) => item.createdAt}
|
|
getAriaLabel={(item) => `选择${item.title}`}
|
|
isSelected={(item) => item.id === 'asset-1'}
|
|
onSelect={onSelect}
|
|
gridClassName="grid grid-cols-1"
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('历史素材读取失败。').className).toContain(
|
|
'text-[var(--platform-button-danger-text)]',
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: '选择历史素材' }));
|
|
|
|
expect(
|
|
screen.getByRole('button', { name: '选择历史素材' }).className,
|
|
).toContain('ring-2');
|
|
expect(onSelect).toHaveBeenCalledWith(
|
|
expect.objectContaining({ id: 'asset-1' }),
|
|
);
|
|
});
|
|
|
|
test('asset grid keeps error banner while loading state remains mutually exclusive with empty state', () => {
|
|
render(
|
|
<PlatformAssetPickerGrid
|
|
items={[]}
|
|
isLoading
|
|
error="历史素材读取失败。"
|
|
loadingLabel="读取中..."
|
|
emptyLabel="暂无历史素材"
|
|
getKey={(item: { id: string }) => item.id}
|
|
getImageSrc={(item) => item.id}
|
|
getImageAlt={() => ''}
|
|
onSelect={() => {}}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('历史素材读取失败。')).toBeTruthy();
|
|
expect(screen.getByText('读取中...')).toBeTruthy();
|
|
expect(screen.queryByText('暂无历史素材')).toBeNull();
|
|
});
|
|
|
|
test('supports dark editor surface with an in-card select affordance', () => {
|
|
const onSelect = vi.fn();
|
|
|
|
render(
|
|
<PlatformAssetPickerGrid
|
|
items={[
|
|
{
|
|
id: 'asset-dark-1',
|
|
imageSrc: '/history/dark.png',
|
|
title: '角色立绘',
|
|
createdAt: '2026-06-09',
|
|
},
|
|
]}
|
|
surface="editorDark"
|
|
selectLabel="使用"
|
|
loadingLabel="读取中..."
|
|
emptyLabel="暂无历史素材"
|
|
getKey={(item) => item.id}
|
|
getImageSrc={(item) => item.imageSrc}
|
|
getImageAlt={(item) => item.title}
|
|
getTitle={(item) => item.title}
|
|
getSubtitle={(item) => item.createdAt}
|
|
onSelect={onSelect}
|
|
/>,
|
|
);
|
|
|
|
const button = screen.getByRole('button', { name: /角色立绘/u });
|
|
|
|
expect(button.className).toContain('bg-black/20');
|
|
expect(screen.getByText('使用').className).toContain('bg-sky-500/12');
|
|
|
|
fireEvent.click(button);
|
|
|
|
expect(onSelect).toHaveBeenCalledWith(
|
|
expect.objectContaining({ id: 'asset-dark-1' }),
|
|
);
|
|
});
|
|
|
|
test('uses dark empty-state chrome for editor asset grids', () => {
|
|
render(
|
|
<PlatformAssetPickerGrid
|
|
items={[]}
|
|
surface="editorDark"
|
|
loadingLabel="读取中..."
|
|
emptyLabel="暂无历史素材"
|
|
getKey={(item: { id: string }) => item.id}
|
|
getImageSrc={(item) => item.id}
|
|
getImageAlt={() => ''}
|
|
onSelect={() => {}}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('暂无历史素材').className).toContain('bg-black/20');
|
|
expect(screen.getByText('暂无历史素材').className).toContain('text-zinc-300');
|
|
});
|