收口前端平台组件库能力
新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件 迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome 补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
This commit is contained in:
164
src/components/common/PlatformSegmentedTabs.test.tsx
Normal file
164
src/components/common/PlatformSegmentedTabs.test.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { expect, test, vi } from 'vitest';
|
||||
|
||||
import { PlatformSegmentedTabs } from './PlatformSegmentedTabs';
|
||||
|
||||
const ITEMS = [
|
||||
{ id: 'work', label: '作品信息' },
|
||||
{ id: 'levels', label: '拼图关卡' },
|
||||
] as const;
|
||||
|
||||
test('renders platform segmented tabs with pressed state', () => {
|
||||
const onChange = vi.fn();
|
||||
|
||||
render(
|
||||
<PlatformSegmentedTabs
|
||||
items={ITEMS}
|
||||
activeId="work"
|
||||
onChange={onChange}
|
||||
className="mb-3"
|
||||
/>,
|
||||
);
|
||||
|
||||
const workTab = screen.getByRole('button', { name: '作品信息' });
|
||||
const levelsTab = screen.getByRole('button', { name: '拼图关卡' });
|
||||
|
||||
expect(workTab.getAttribute('aria-pressed')).toBe('true');
|
||||
expect(levelsTab.getAttribute('aria-pressed')).toBe('false');
|
||||
expect(workTab.className).toContain('bg-white');
|
||||
expect(levelsTab.className).toContain('hover:bg-white/60');
|
||||
expect(workTab.closest('div')?.className).toContain('grid-cols-2');
|
||||
expect(workTab.closest('div')?.className).toContain('mb-3');
|
||||
|
||||
fireEvent.click(levelsTab);
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith('levels');
|
||||
});
|
||||
|
||||
test('supports compact responsive columns and truncated labels', () => {
|
||||
render(
|
||||
<PlatformSegmentedTabs
|
||||
items={[
|
||||
{ id: 'profile', label: '作品' },
|
||||
{ id: 'world', label: '世界' },
|
||||
{ id: 'opening', label: '开场' },
|
||||
]}
|
||||
activeId="profile"
|
||||
onChange={vi.fn()}
|
||||
columns="threeToSix"
|
||||
gap="sm"
|
||||
radius="md"
|
||||
size="compact"
|
||||
truncateLabels
|
||||
/>,
|
||||
);
|
||||
|
||||
const profileTab = screen.getByRole('button', { name: '作品' });
|
||||
const label = profileTab.querySelector('span');
|
||||
|
||||
expect(profileTab.closest('div')?.className).toContain('grid-cols-3');
|
||||
expect(profileTab.closest('div')?.className).toContain('sm:grid-cols-6');
|
||||
expect(profileTab.closest('div')?.className).toContain('gap-1');
|
||||
expect(profileTab.className).toContain('rounded-[0.8rem]');
|
||||
expect(profileTab.className).toContain('text-xs');
|
||||
expect(label?.className).toContain('truncate');
|
||||
});
|
||||
|
||||
test('respects disabled items and custom item classes', () => {
|
||||
const onChange = vi.fn();
|
||||
|
||||
render(
|
||||
<PlatformSegmentedTabs
|
||||
items={[
|
||||
{ id: 'single', label: '单关卡' },
|
||||
{ id: 'multi', label: '多关卡', disabled: true },
|
||||
]}
|
||||
activeId="single"
|
||||
onChange={onChange}
|
||||
itemClassName={(item, active) =>
|
||||
item.id === 'single' && active ? 'data-active-token' : null
|
||||
}
|
||||
/>,
|
||||
);
|
||||
|
||||
const singleTab = screen.getByRole('button', { name: '单关卡' });
|
||||
const multiTab = screen.getByRole('button', { name: '多关卡' });
|
||||
|
||||
expect(singleTab.className).toContain('data-active-token');
|
||||
expect(multiTab).toHaveProperty('disabled', true);
|
||||
|
||||
fireEvent.click(multiTab);
|
||||
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('supports bare four-column choice tabs with tone variants', () => {
|
||||
render(
|
||||
<PlatformSegmentedTabs
|
||||
items={[
|
||||
{ id: 'easy', label: '轻松' },
|
||||
{ id: 'standard', label: '标准' },
|
||||
{ id: 'advanced', label: '进阶' },
|
||||
{ id: 'hardcore', label: '硬核' },
|
||||
]}
|
||||
activeId="advanced"
|
||||
onChange={vi.fn()}
|
||||
columns="four"
|
||||
size="choice"
|
||||
surface="transparent"
|
||||
tone="rose"
|
||||
frame="bare"
|
||||
/>,
|
||||
);
|
||||
|
||||
const advancedTab = screen.getByRole('button', { name: '进阶' });
|
||||
const standardTab = screen.getByRole('button', { name: '标准' });
|
||||
const tabGrid = advancedTab.closest('div');
|
||||
|
||||
expect(tabGrid?.className).toContain('grid-cols-4');
|
||||
expect(tabGrid?.className).toContain('border-0');
|
||||
expect(tabGrid?.className).toContain('p-0');
|
||||
expect(tabGrid?.className).toContain('bg-transparent');
|
||||
expect(advancedTab.className).toContain('rounded-[0.9rem]');
|
||||
expect(advancedTab.className).toContain('bg-[linear-gradient');
|
||||
expect(standardTab.className).toContain('bg-white/76');
|
||||
});
|
||||
|
||||
test('supports auth-style tab semantics with underline tone', () => {
|
||||
const onChange = vi.fn();
|
||||
|
||||
render(
|
||||
<PlatformSegmentedTabs
|
||||
items={[
|
||||
{ id: 'phone', label: '短信登录' },
|
||||
{ id: 'password', label: '密码登录' },
|
||||
]}
|
||||
activeId="phone"
|
||||
onChange={onChange}
|
||||
columns="one"
|
||||
frame="bare"
|
||||
surface="transparent"
|
||||
tone="underline"
|
||||
size="tab"
|
||||
semantics="tabs"
|
||||
ariaLabel="登录方式"
|
||||
/>,
|
||||
);
|
||||
|
||||
const tablist = screen.getByRole('tablist', { name: '登录方式' });
|
||||
const phoneTab = screen.getByRole('tab', { name: '短信登录' });
|
||||
const passwordTab = screen.getByRole('tab', { name: '密码登录' });
|
||||
|
||||
expect(tablist.className).toContain('grid-cols-1');
|
||||
expect(phoneTab.getAttribute('aria-selected')).toBe('true');
|
||||
expect(phoneTab.getAttribute('aria-pressed')).toBeNull();
|
||||
expect(phoneTab.className).toContain('h-12');
|
||||
expect(phoneTab.querySelector('span.absolute')).toBeTruthy();
|
||||
expect(passwordTab.getAttribute('aria-selected')).toBe('false');
|
||||
|
||||
fireEvent.click(passwordTab);
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith('password');
|
||||
});
|
||||
Reference in New Issue
Block a user