收口前端平台组件库能力

新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件
迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome
补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
This commit is contained in:
2026-06-10 10:24:18 +08:00
parent a4ee6ff698
commit 1ad25e30f8
226 changed files with 23364 additions and 7825 deletions

View File

@@ -0,0 +1,167 @@
/* @vitest-environment jsdom */
import { render, screen } from '@testing-library/react';
import { expect, test } from 'vitest';
import { PlatformIconButton } from './PlatformIconButton';
test('renders platform icon button with accessible label and icon chrome', () => {
render(
<PlatformIconButton
label="关闭"
icon={<span aria-hidden="true">×</span>}
/>,
);
const button = screen.getByRole('button', { name: '关闭' });
expect(button.className).toContain('platform-icon-button');
expect(button.getAttribute('type')).toBe('button');
expect(button.textContent).toBe('×');
});
test('keeps local class names and explicit title', () => {
render(
<PlatformIconButton
label="发送"
title="发送"
icon={<span aria-hidden="true"></span>}
className="h-11 w-11"
/>,
);
const button = screen.getByRole('button', { name: '发送' });
expect(button.className).toContain('h-11');
expect(button.getAttribute('title')).toBe('发送');
});
test('supports floating surface icon action chrome', () => {
render(
<PlatformIconButton
label="更换图片"
title="更换图片"
variant="surfaceFloating"
icon={<span aria-hidden="true">+</span>}
className="h-10 w-10"
/>,
);
const button = screen.getByRole('button', { name: '更换图片' });
expect(button.className).toContain('bg-white/94');
expect(button.className).toContain('backdrop-blur');
expect(button.className).toContain('h-10');
expect(button.className).not.toContain('platform-icon-button');
});
test('supports visible short label on floating surface actions', () => {
render(
<PlatformIconButton
label="选择历史图片"
title="选择历史图片"
variant="surfaceFloating"
icon={<span aria-hidden="true"></span>}
className="gap-1.5 px-3"
>
<span></span>
</PlatformIconButton>,
);
const button = screen.getByRole('button', { name: '选择历史图片' });
expect(button.textContent).toContain('历史');
expect(button.className).toContain('bg-white/94');
expect(button.className).toContain('gap-1.5');
expect(button.className).toContain('px-3');
});
test('supports dark mini icon action chrome', () => {
render(
<PlatformIconButton
label="字段说明"
variant="darkMini"
icon={<span aria-hidden="true">?</span>}
className="h-4 w-4 text-[10px]"
/>,
);
const button = screen.getByRole('button', { name: '字段说明' });
expect(button.className).toContain('bg-black/55');
expect(button.className).toContain('border-white/16');
expect(button.className).toContain('hover:bg-black/70');
expect(button.className).toContain('h-4');
expect(button.textContent).toBe('?');
});
test('supports label child chrome for icon upload controls', () => {
const { container } = render(
<>
<PlatformIconButton
asChild="label"
htmlFor="reference-image"
label="上传参考图"
title="上传参考图"
icon={<span aria-hidden="true" />}
className="h-9 w-9 cursor-pointer"
/>
<input id="reference-image" type="file" />
</>,
);
const input = screen.getByLabelText('上传参考图');
const label = container.querySelector('label[for="reference-image"]');
expect(input.getAttribute('type')).toBe('file');
expect(label?.tagName).toBe('LABEL');
expect(label?.getAttribute('for')).toBe('reference-image');
expect(label?.className).toContain('platform-icon-button');
expect(label?.className).toContain('cursor-pointer');
expect(label?.getAttribute('title')).toBe('上传参考图');
});
test('supports floating surface label upload controls', () => {
const { container } = render(
<PlatformIconButton
asChild="label"
label="上传参考图"
variant="surfaceFloating"
title="上传参考图"
icon={
<>
<span aria-hidden="true">+</span>
<input type="file" />
</>
}
className="h-8 w-8 cursor-pointer"
/>,
);
const label = container.querySelector('label');
const input = label?.querySelector('input');
expect(label?.textContent).toContain('上传参考图');
expect(input?.getAttribute('type')).toBe('file');
expect(label?.className).toContain('bg-white/94');
expect(label?.className).toContain('cursor-pointer');
});
test('keeps nested file input associated with the label name', () => {
render(
<PlatformIconButton
asChild="label"
label="上传参考图"
icon={
<>
<span aria-hidden="true" />
<input type="file" />
</>
}
/>,
);
const input = screen.getByLabelText('上传参考图', { selector: 'input' });
expect(input.getAttribute('type')).toBe('file');
});