收口前端平台组件库能力

新增 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,85 @@
/* @vitest-environment jsdom */
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { afterEach, describe, expect, test, vi } from 'vitest';
import * as clipboardService from '../../services/clipboard';
import { useCopyFeedback } from './useCopyFeedback';
vi.mock('../../services/clipboard', () => ({
copyTextToClipboard: vi.fn(),
}));
function CopyFeedbackHarness({
value,
resetDelayMs = 1400,
}: {
value: string;
resetDelayMs?: number;
}) {
const { copyState, copyText, resetCopyState } = useCopyFeedback({
resetDelayMs,
});
return (
<div>
<div data-testid="copy-state">{copyState}</div>
<button
type="button"
onClick={() => {
void copyText(value);
}}
>
</button>
<button type="button" onClick={resetCopyState}>
</button>
</div>
);
}
afterEach(() => {
vi.clearAllMocks();
vi.useRealTimers();
});
describe('useCopyFeedback', () => {
test('copies text and resets after the feedback delay', async () => {
vi.useFakeTimers();
vi.mocked(clipboardService.copyTextToClipboard).mockResolvedValue(true);
render(<CopyFeedbackHarness value="作品号PZ-1" resetDelayMs={10} />);
fireEvent.click(screen.getByRole('button', { name: '复制' }));
await act(async () => {
await Promise.resolve();
});
expect(screen.getByTestId('copy-state').textContent).toBe('copied');
expect(clipboardService.copyTextToClipboard).toHaveBeenCalledWith(
'作品号PZ-1',
);
act(() => {
vi.advanceTimersByTime(10);
});
expect(screen.getByTestId('copy-state').textContent).toBe('idle');
});
test('keeps failure state until reset or timeout', async () => {
vi.mocked(clipboardService.copyTextToClipboard).mockResolvedValue(false);
render(<CopyFeedbackHarness value="" />);
fireEvent.click(screen.getByRole('button', { name: '复制' }));
await waitFor(() => {
expect(screen.getByTestId('copy-state').textContent).toBe('failed');
});
fireEvent.click(screen.getByRole('button', { name: '重置' }));
expect(screen.getByTestId('copy-state').textContent).toBe('idle');
});
});