126 lines
4.0 KiB
TypeScript
126 lines
4.0 KiB
TypeScript
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<typeof createRoot> | 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(<RefreshButton onRefresh={onRefresh} />);
|
||
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(<RefreshButton onRefresh={onRefresh} loading />);
|
||
expect(container?.querySelector('.ant-btn-loading')).toBeTruthy();
|
||
});
|
||
|
||
it('useSubmitShortcut 未激活时不响应 Cmd+Enter', async () => {
|
||
const onSubmit = vi.fn();
|
||
const Harness = () => {
|
||
useSubmitShortcut(false, onSubmit);
|
||
return <button type="button">ok</button>;
|
||
};
|
||
mount(<Harness />);
|
||
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 <button type="button">ok</button>;
|
||
};
|
||
mount(<Harness />);
|
||
|
||
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(<BackTop threshold={-1} />);
|
||
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 (
|
||
<button type="button" onClick={() => navigate('/other')}>
|
||
go
|
||
</button>
|
||
);
|
||
};
|
||
mount(
|
||
<MemoryRouter initialEntries={['/']}>
|
||
<ScrollToTop />
|
||
<Routes>
|
||
<Route path="/" element={<Nav />} />
|
||
<Route path="/other" element={<div>other</div>} />
|
||
</Routes>
|
||
</MemoryRouter>,
|
||
);
|
||
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);
|
||
});
|
||
});
|