/** * Tests for the useClickOutside hook * @jest-environment jsdom */ import { renderHook } from '@testing-library/react'; import { useClickOutside } from '@/hooks/useClickOutside'; import { createRef } from 'react'; describe('useClickOutside', () => { let callback: jest.Mock; let ref: React.RefObject; let element: HTMLDivElement; beforeEach(() => { callback = jest.fn(); element = document.createElement('div'); document.body.appendChild(element); ref = { current: element }; }); afterEach(() => { document.body.removeChild(element); jest.clearAllMocks(); }); it('should call callback when clicking outside the element', () => { renderHook(() => useClickOutside(ref, callback, true)); const outsideElement = document.createElement('div'); document.body.appendChild(outsideElement); const event = new MouseEvent('mousedown', { bubbles: true }); outsideElement.dispatchEvent(event); expect(callback).toHaveBeenCalledTimes(1); document.body.removeChild(outsideElement); }); it('should not call callback when clicking inside the element', () => { renderHook(() => useClickOutside(ref, callback, true)); const event = new MouseEvent('mousedown', { bubbles: true }); element.dispatchEvent(event); expect(callback).not.toHaveBeenCalled(); }); it('should not call callback when disabled', () => { renderHook(() => useClickOutside(ref, callback, false)); const outsideElement = document.createElement('div'); document.body.appendChild(outsideElement); const event = new MouseEvent('mousedown', { bubbles: true }); outsideElement.dispatchEvent(event); expect(callback).not.toHaveBeenCalled(); document.body.removeChild(outsideElement); }); it('should handle null ref gracefully', () => { const nullRef = createRef(); // Should not throw when ref is null expect(() => { renderHook(() => useClickOutside(nullRef, callback, true)); const event = new MouseEvent('mousedown', { bubbles: true }); document.body.dispatchEvent(event); }).not.toThrow(); // Callback should NOT be called when ref is null (ref.current is falsy) expect(callback).not.toHaveBeenCalled(); }); it('should remove event listener on unmount', () => { const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener'); const { unmount } = renderHook(() => useClickOutside(ref, callback, true)); unmount(); expect(removeEventListenerSpy).toHaveBeenCalledWith('mousedown', expect.any(Function)); removeEventListenerSpy.mockRestore(); }); it('should use default enabled value of true', () => { renderHook(() => useClickOutside(ref, callback)); const outsideElement = document.createElement('div'); document.body.appendChild(outsideElement); const event = new MouseEvent('mousedown', { bubbles: true }); outsideElement.dispatchEvent(event); expect(callback).toHaveBeenCalledTimes(1); document.body.removeChild(outsideElement); }); });