收口前端平台组件库能力

新增 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

@@ -98,6 +98,23 @@ function buildSession(
};
}
function findNearestClassName(
element: HTMLElement,
classNamePart: string,
): HTMLElement | null {
let current: HTMLElement | null = element;
while (current) {
if (current.className.includes(classNamePart)) {
return current;
}
current = current.parentElement;
}
return null;
}
test('settings header uses a generic title instead of the phone number', () => {
renderAccountModal();
@@ -119,6 +136,27 @@ test('settings header uses a generic title instead of the phone number', () => {
expect(screen.getByRole('button', { name: //u })).toBeTruthy();
expect(screen.queryByRole('button', { name: //u })).toBeNull();
expect(screen.queryByRole('button', { name: //u })).toBeNull();
const themeSettingsButton = screen.getByRole('button', { name: //u });
expect(themeSettingsButton.getAttribute('type')).toBe('button');
expect(themeSettingsButton.className).toContain('platform-subpanel');
expect(themeSettingsButton.className).toContain('rounded-[1.5rem]');
expect(themeSettingsButton.className).toContain('hover:bg-white');
});
test('appearance panel uses PlatformPillBadge for current theme status', async () => {
const user = userEvent.setup();
renderAccountModal();
await user.click(screen.getByRole('button', { name: //u }));
const appearanceDialog = screen.getByRole('dialog', { name: '主题设置' });
const themeStatusBadge = within(appearanceDialog).getByText('平台设置已同步');
expect(within(appearanceDialog).getByText('当前主题')).toBeTruthy();
expect(themeStatusBadge.className).toContain('rounded-full');
expect(themeStatusBadge.className).toContain('bg-white/72');
});
test('direct account entry does not render the settings shell as another dialog', () => {
@@ -159,6 +197,9 @@ test('account panel uses compact binding cards and keeps logout actions at the b
'[data-account-binding-card]',
);
expect(compactCards).toHaveLength(2);
expect(compactCards[0]?.className).toContain('platform-subpanel');
expect(compactCards[0]?.className).toContain('rounded-[1rem]');
expect(compactCards[0]?.className).toContain('px-3.5 py-3');
expect(
within(compactCards[0] as HTMLElement).getByRole('button', {
name: '更换手机号',
@@ -357,6 +398,18 @@ test('account panel includes merged security devices and audit sections', async
expect(within(accountDialog).getByText('手机号保护')).toBeTruthy();
expect(within(accountDialog).getByText('iPhone 15 Pro')).toBeTruthy();
expect(within(accountDialog).getByText('登录成功')).toBeTruthy();
const deviceRow = findNearestClassName(
within(accountDialog).getByText('iPhone 15 Pro'),
'bg-white/72',
);
const auditRow = findNearestClassName(
within(accountDialog).getByText('登录成功'),
'bg-white/72',
);
expect(deviceRow?.className).toContain('rounded-[1rem]');
expect(deviceRow?.className).toContain('px-4 py-3');
expect(auditRow?.className).toContain('rounded-[1rem]');
expect(auditRow?.className).toContain('px-4 py-3');
expect(
within(accountDialog).getByRole('button', { name: '退出登录' }),
).toBeTruthy();
@@ -392,7 +445,14 @@ test('current merged session group hides kick action and shows count', async ()
await user.click(screen.getByRole('button', { name: /账号与安全/ }));
const accountDialog = screen.getByRole('dialog', { name: '账号信息' });
const sessionCountBadge = within(accountDialog).getByText('2 个会话');
const currentDeviceBadge = within(accountDialog).getByText('当前设备');
expect(within(accountDialog).getByText('2 个会话')).toBeTruthy();
expect(sessionCountBadge.className).toContain('rounded-full');
expect(sessionCountBadge.className).toContain('bg-white/72');
expect(currentDeviceBadge.className).toContain('rounded-full');
expect(currentDeviceBadge.className).toContain('border-emerald-200');
expect(
within(accountDialog).queryByRole('button', { name: '踢下线' }),
).toBeNull();
@@ -419,8 +479,12 @@ test('remote merged session group can be revoked with loading state', async () =
const revokeButton = within(accountDialog).getByRole('button', {
name: '处理中...',
}) as HTMLButtonElement;
const loggedInBadge = within(accountDialog).getByText('已登录');
expect(revokeButton.disabled).toBe(true);
expect(within(accountDialog).getByText('2 个会话')).toBeTruthy();
expect(loggedInBadge.className).toContain('rounded-full');
expect(loggedInBadge.className).toContain('border-emerald-200');
expect(onRevokeSession).not.toHaveBeenCalled();
});