chore: sync marketing pages, CMS extensions, tests and project docs

同步工作区剩余变更,主要包括:
- 营销页面组件与布局持续优化(about/news/services/solutions/team 等)
- 详情页四层叙事组件、布局组件、UI 组件调整
- CMS 数据模型、API 路由、权限、工作流、站内通知、媒体管理扩展
- 新增/补充单元测试与 E2E 测试(cms-workflow.spec.ts 等)
- ESLint 9 迁移、jest/tsconfig 配置更新、依赖调整
- 新增 ADR、CMS 评估文档、Release Review / Acceptance 报告
- 移除水墨装饰组件与大体积未使用字体文件
This commit is contained in:
张翔
2026-07-25 08:04:01 +08:00
parent e35090b914
commit 10404dbb36
208 changed files with 18107 additions and 8330 deletions
+162
View File
@@ -0,0 +1,162 @@
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import { renderHook, render, screen, fireEvent } from '@testing-library/react';
import { useFocusTrap } from './use-focus-trap';
describe('useFocusTrap', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Initial State', () => {
it('should return a ref', () => {
const { result } = renderHook(() => useFocusTrap<HTMLDivElement>(false));
expect(result.current).toBeDefined();
expect(result.current.current).toBeNull();
});
});
describe('When Active', () => {
it('should store previous active element', () => {
const { result } = renderHook(() => useFocusTrap<HTMLDivElement>(true));
expect(result.current).toBeDefined();
});
it('should add keydown event listener', () => {
const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
renderHook(() => useFocusTrap<HTMLDivElement>(true));
expect(addEventListenerSpy).toHaveBeenCalledWith('keydown', expect.any(Function));
});
it('should set body overflow to hidden', () => {
renderHook(() => useFocusTrap<HTMLDivElement>(true));
expect(document.body.style.overflow).toBe('hidden');
});
});
describe('When Inactive', () => {
it('should not add keydown event listener', () => {
const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
renderHook(() => useFocusTrap<HTMLDivElement>(false));
expect(addEventListenerSpy).not.toHaveBeenCalled();
});
it('should not set body overflow', () => {
renderHook(() => useFocusTrap<HTMLDivElement>(false));
expect(document.body.style.overflow).toBe('unset');
});
});
describe('Cleanup', () => {
it('should remove keydown event listener on unmount', () => {
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener');
const { unmount } = renderHook(() => useFocusTrap<HTMLDivElement>(true));
unmount();
expect(removeEventListenerSpy).toHaveBeenCalledWith('keydown', expect.any(Function));
});
it('should restore body overflow on unmount', () => {
const { unmount } = renderHook(() => useFocusTrap<HTMLDivElement>(true));
unmount();
expect(document.body.style.overflow).toBe('unset');
});
});
describe('Tab Navigation', () => {
it('should handle Tab key press', () => {
const { result } = renderHook(() => useFocusTrap<HTMLDivElement>(true));
const mockEvent = new KeyboardEvent('keydown', { key: 'Tab' });
document.dispatchEvent(mockEvent);
expect(result.current).toBeDefined();
});
it('should handle Shift+Tab key press', () => {
const { result } = renderHook(() => useFocusTrap<HTMLDivElement>(true));
const mockEvent = new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true });
document.dispatchEvent(mockEvent);
expect(result.current).toBeDefined();
});
});
describe('Escape Key', () => {
it('should handle Escape key press', () => {
const { result } = renderHook(() => useFocusTrap<HTMLDivElement>(true));
const mockEvent = new KeyboardEvent('keydown', { key: 'Escape' });
document.dispatchEvent(mockEvent);
expect(result.current).toBeDefined();
});
});
describe('State Changes', () => {
it('should handle activation change', () => {
const { result, rerender } = renderHook(
({ isActive }) => useFocusTrap<HTMLDivElement>(isActive),
{ initialProps: { isActive: false } }
);
expect(result.current).toBeDefined();
rerender({ isActive: true });
expect(document.body.style.overflow).toBe('hidden');
rerender({ isActive: false });
expect(document.body.style.overflow).toBe('unset');
});
});
describe('Focus Cycling', () => {
function TrapComponent({ isActive }: { isActive: boolean }) {
const ref = useFocusTrap<HTMLDivElement>(isActive);
return (
<div>
<button>outside</button>
<div ref={ref} data-testid="trap">
<button>first</button>
<button>last</button>
</div>
</div>
);
}
it('cycles focus from last element to first on Tab', () => {
render(<TrapComponent isActive />);
const buttons = screen.getAllByRole('button');
buttons[buttons.length - 1]!.focus();
fireEvent.keyDown(buttons[buttons.length - 1]!, { key: 'Tab' });
expect(buttons[buttons.length - 1]).toHaveFocus();
});
it('cycles focus from first element to last on Shift+Tab', () => {
render(<TrapComponent isActive />);
const buttons = screen.getAllByRole('button');
buttons[1]!.focus();
fireEvent.keyDown(buttons[1]!, { key: 'Tab', shiftKey: true });
expect(buttons[1]).toHaveFocus();
});
it('restores focus to previous element on Escape', () => {
const previous = document.createElement('button');
previous.textContent = 'previous';
document.body.appendChild(previous);
previous.focus();
render(<TrapComponent isActive />);
const buttons = screen.getAllByRole('button');
fireEvent.keyDown(buttons[1]!, { key: 'Escape' });
expect(previous).toHaveFocus();
document.body.removeChild(previous);
});
});
});