import React, { act } from 'react'; import { createRoot } from 'react-dom/client'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { MemoryRouter, Route, Routes, useNavigate } from 'react-router'; import { RefreshButton } from './RefreshButton'; import { BackTop } from './BackTop'; import { ScrollToTop } from './ScrollToTop'; import { useSubmitShortcut } from '../hooks/useSubmitShortcut'; let container: HTMLDivElement | null = null; let root: ReturnType | null = null; const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); const mount = (node: React.ReactNode) => { const host = document.createElement('div'); container = host; document.body.appendChild(host); root = createRoot(host); act(() => root?.render(node)); }; afterEach(async () => { if (root) { await act(async () => root?.unmount()); } container?.remove(); root = null; container = null; vi.restoreAllMocks(); }); describe('UX 组件与交互', () => { it('RefreshButton 点击触发 onRefresh,loading 时展示加载态', async () => { const onRefresh = vi.fn(); mount(); const button = container?.querySelector('button'); if (!button) throw new Error('button not rendered'); act(() => button.dispatchEvent(new MouseEvent('click', { bubbles: true }))); await flush(); expect(onRefresh).toHaveBeenCalledTimes(1); mount(); expect(container?.querySelector('.ant-btn-loading')).toBeTruthy(); }); it('useSubmitShortcut 未激活时不响应 Cmd+Enter', async () => { const onSubmit = vi.fn(); const Harness = () => { useSubmitShortcut(false, onSubmit); return ; }; mount(); window.dispatchEvent( new KeyboardEvent('keydown', { key: 'Enter', metaKey: true, bubbles: true }), ); await flush(); expect(onSubmit).not.toHaveBeenCalled(); }); it('useSubmitShortcut 激活时响应 Cmd/Ctrl+Enter 且阻止默认行为', async () => { const onSubmit = vi.fn(); const Harness = () => { useSubmitShortcut(true, onSubmit); return ; }; mount(); const metaEvent = new KeyboardEvent('keydown', { key: 'Enter', metaKey: true, bubbles: true, cancelable: true, }); window.dispatchEvent(metaEvent); expect(metaEvent.defaultPrevented).toBe(true); window.dispatchEvent( new KeyboardEvent('keydown', { key: 'Enter', ctrlKey: true, bubbles: true }), ); await flush(); expect(onSubmit).toHaveBeenCalledTimes(2); }); it('BackTop 超过阈值后出现,点击回到顶部', async () => { const scrollSpy = vi.spyOn(window, 'scrollTo').mockImplementation(() => {}); mount(); await flush(); const button = container?.querySelector('button'); expect(button).toBeTruthy(); if (button) { act(() => button.dispatchEvent(new MouseEvent('click', { bubbles: true }))); } await flush(); expect(scrollSpy).toHaveBeenCalledWith(expect.objectContaining({ top: 0 })); }); it('ScrollToTop 在路由切换时把滚动位置复位到顶部', async () => { const scrollSpy = vi.spyOn(window, 'scrollTo').mockImplementation(() => {}); const Nav = () => { const navigate = useNavigate(); return ( ); }; mount( } /> other} /> , ); scrollSpy.mockClear(); const button = container?.querySelector('button'); if (!button) throw new Error('button not rendered'); act(() => button.dispatchEvent(new MouseEvent('click', { bubbles: true }))); await flush(); expect(scrollSpy).toHaveBeenCalledWith(0, 0); }); });