test(unit): expand unit tests and fix stress test robustness

- Add comprehensive useFocusTrap tests (edge cases, disabled elements, empty containers)
- Add useSwipeGesture tests (haptic feedback, touch events, disabled state, effect deps)
- Enhance animations.test.tsx with Framer Motion mock and component tests
- Fix stress-test.js body.length undefined error when requests fail
- Fix home-content-v14.tsx for consistency
- Clean up generated performance test summary files
This commit is contained in:
2026-07-31 20:25:26 +08:00
parent 3e629bd9ee
commit 41b73063cd
7 changed files with 613 additions and 549 deletions
+123
View File
@@ -6,6 +6,7 @@ import { useFocusTrap } from './use-focus-trap';
describe('useFocusTrap', () => {
beforeEach(() => {
jest.clearAllMocks();
document.body.style.overflow = 'unset';
});
describe('Initial State', () => {
@@ -18,8 +19,14 @@ describe('useFocusTrap', () => {
describe('When Active', () => {
it('should store previous active element', () => {
const activeElement = document.createElement('button');
document.body.appendChild(activeElement);
activeElement.focus();
const { result } = renderHook(() => useFocusTrap<HTMLDivElement>(true));
expect(result.current).toBeDefined();
document.body.removeChild(activeElement);
});
it('should add keydown event listener', () => {
@@ -45,6 +52,34 @@ describe('useFocusTrap', () => {
renderHook(() => useFocusTrap<HTMLDivElement>(false));
expect(document.body.style.overflow).toBe('unset');
});
it('should recreate handleKeyDown when isActive changes', () => {
const addSpy = jest.spyOn(document, 'addEventListener');
const { rerender } = renderHook(
({ isActive }) => useFocusTrap<HTMLDivElement>(isActive),
{ initialProps: { isActive: false } }
);
// No handlers registered when inactive
let keydownHandlers = addSpy.mock.calls.filter(c => c[0] === 'keydown');
expect(keydownHandlers.length).toBe(0);
// Activate: handler is registered
rerender({ isActive: true });
keydownHandlers = addSpy.mock.calls.filter(c => c[0] === 'keydown');
expect(keydownHandlers.length).toBe(1);
// Deactivate and reactivate: new handler should be created
rerender({ isActive: false });
rerender({ isActive: true });
keydownHandlers = addSpy.mock.calls.filter(c => c[0] === 'keydown');
// With real deps, a new handler is created (2nd addEventListener call)
// With [] deps, it calls addEventListener 2 times (remove + add = no-op)
// but the actual addEventListener still gets called
expect(keydownHandlers.length).toBeGreaterThanOrEqual(2);
});
});
describe('Cleanup', () => {
@@ -60,6 +95,18 @@ describe('useFocusTrap', () => {
unmount();
expect(document.body.style.overflow).toBe('unset');
});
it('should clean up event listener when deactivating', () => {
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener');
const { rerender } = renderHook(
({ isActive }) => useFocusTrap<HTMLDivElement>(isActive),
{ initialProps: { isActive: true } }
);
rerender({ isActive: false });
expect(removeEventListenerSpy).toHaveBeenCalledWith('keydown', expect.any(Function));
});
});
describe('Tab Navigation', () => {
@@ -91,6 +138,16 @@ describe('useFocusTrap', () => {
expect(result.current).toBeDefined();
});
it('should not crash when Escape is pressed and no previous element exists', () => {
// Render with isActive=false so no event listener is registered
renderHook(() => useFocusTrap<HTMLDivElement>(false));
// Dispatch Escape - should not crash since no handler is registered
expect(() => {
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
}).not.toThrow();
});
});
describe('State Changes', () => {
@@ -158,5 +215,71 @@ describe('useFocusTrap', () => {
expect(previous).toHaveFocus();
document.body.removeChild(previous);
});
it('should prevent default on Escape key', () => {
render(<TrapComponent isActive />);
const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true });
const escapePreventDefaultSpy = jest.spyOn(escapeEvent, 'preventDefault');
document.dispatchEvent(escapeEvent);
expect(escapePreventDefaultSpy).toHaveBeenCalled();
});
});
describe('Edge Cases', () => {
it('should handle empty container with no focusable elements', () => {
function EmptyContainer() {
const ref = useFocusTrap<HTMLDivElement>(true);
return <div ref={ref} data-testid="empty" />;
}
render(<EmptyContainer />);
// Dispatch Tab - should not crash
expect(() => {
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }));
}).not.toThrow();
});
it('should handle disabled focusable elements', () => {
function ContainerWithDisabled() {
const ref = useFocusTrap<HTMLDivElement>(true);
return (
<div ref={ref}>
<button disabled>disabled</button>
<button>enabled</button>
</div>
);
}
render(<ContainerWithDisabled />);
const buttons = screen.getAllByRole('button');
// Only the enabled button is focusable
buttons[1]!.focus();
// Tab from last enabled element should cycle to first enabled element
fireEvent.keyDown(buttons[1]!, { key: 'Tab' });
expect(buttons[1]).toHaveFocus();
});
it('should handle hidden focusable elements', () => {
function ContainerWithHidden() {
const ref = useFocusTrap<HTMLDivElement>(true);
return (
<div ref={ref}>
<button style={{ display: 'none' }}>hidden</button>
<button>visible</button>
</div>
);
}
render(<ContainerWithHidden />);
// Only the visible button should be focusable
expect(() => {
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }));
}).not.toThrow();
});
});
});