import { renderHook, act, waitFor } from '@testing-library/react' import { useResponsive, DEFAULT_BREAKPOINTS } from '../../hooks/useResponsive' describe('useResponsive', () => { // Mock window.innerWidth const setWindowWidth = (width: number) => { Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: width, }) } // Trigger resize event const triggerResize = (width: number) => { setWindowWidth(width) window.dispatchEvent(new Event('resize')) } beforeEach(() => { // Reset to desktop size by default setWindowWidth(1440) }) describe('Initialization', () => { it('should initialize with desktop view mode for large screens', () => { setWindowWidth(1440) const { result } = renderHook(() => useResponsive()) expect(result.current.viewMode).toBe('desktop') expect(result.current.isDesktop).toBe(true) expect(result.current.isMobile).toBe(false) expect(result.current.isTablet).toBe(false) expect(result.current.isMobileOrTablet).toBe(false) }) it('should initialize with tablet view mode for medium screens', async () => { setWindowWidth(800) const { result } = renderHook(() => useResponsive()) await waitFor(() => { expect(result.current.viewMode).toBe('tablet') }) expect(result.current.isTablet).toBe(true) expect(result.current.isMobile).toBe(false) expect(result.current.isDesktop).toBe(false) expect(result.current.isMobileOrTablet).toBe(true) }) it('should initialize with mobile view mode for small screens', async () => { setWindowWidth(500) const { result } = renderHook(() => useResponsive()) await waitFor(() => { expect(result.current.viewMode).toBe('mobile') }) expect(result.current.isMobile).toBe(true) expect(result.current.isTablet).toBe(false) expect(result.current.isDesktop).toBe(false) expect(result.current.isMobileOrTablet).toBe(true) }) it('should track window width', () => { setWindowWidth(1200) const { result } = renderHook(() => useResponsive()) expect(result.current.windowWidth).toBeGreaterThan(0) }) }) describe('Default Breakpoints', () => { it('should use default mobile breakpoint (768px)', async () => { setWindowWidth(767) const { result } = renderHook(() => useResponsive()) await waitFor(() => { expect(result.current.viewMode).toBe('mobile') }) }) it('should use default tablet breakpoint (1024px)', async () => { setWindowWidth(1023) const { result } = renderHook(() => useResponsive()) await waitFor(() => { expect(result.current.viewMode).toBe('tablet') }) }) it('should use default desktop breakpoint (1280px)', async () => { setWindowWidth(1280) const { result } = renderHook(() => useResponsive()) await waitFor(() => { expect(result.current.viewMode).toBe('desktop') }) }) }) describe('Custom Breakpoints', () => { it('should accept custom breakpoints', async () => { const customBreakpoints = { mobile: 600, tablet: 900, desktop: 1200, } setWindowWidth(650) const { result } = renderHook(() => useResponsive(customBreakpoints)) await waitFor(() => { expect(result.current.viewMode).toBe('tablet') }) }) it('should use custom mobile breakpoint', async () => { const customBreakpoints = { mobile: 600, tablet: 900, desktop: 1200, } setWindowWidth(550) const { result } = renderHook(() => useResponsive(customBreakpoints)) await waitFor(() => { expect(result.current.viewMode).toBe('mobile') }) }) it('should use custom desktop breakpoint', async () => { const customBreakpoints = { mobile: 600, tablet: 900, desktop: 1200, } setWindowWidth(1250) const { result } = renderHook(() => useResponsive(customBreakpoints)) await waitFor(() => { expect(result.current.viewMode).toBe('desktop') }) }) }) describe('Resize Handling', () => { it('should update viewMode on window resize', async () => { const { result } = renderHook(() => useResponsive()) // Start at desktop setWindowWidth(1440) triggerResize(1440) await waitFor(() => { expect(result.current.viewMode).toBe('desktop') }) // Resize to tablet act(() => { triggerResize(800) }) await waitFor(() => { expect(result.current.viewMode).toBe('tablet') }) // Resize to mobile act(() => { triggerResize(500) }) await waitFor(() => { expect(result.current.viewMode).toBe('mobile') }) }) it('should update windowWidth on resize', async () => { const { result } = renderHook(() => useResponsive()) act(() => { triggerResize(1200) }) await waitFor(() => { expect(result.current.windowWidth).toBe(1200) }) act(() => { triggerResize(800) }) await waitFor(() => { expect(result.current.windowWidth).toBe(800) }) }) it('should update boolean flags on resize', async () => { const { result } = renderHook(() => useResponsive()) // Desktop act(() => { triggerResize(1440) }) await waitFor(() => { expect(result.current.isDesktop).toBe(true) expect(result.current.isTablet).toBe(false) expect(result.current.isMobile).toBe(false) }) // Tablet act(() => { triggerResize(800) }) await waitFor(() => { expect(result.current.isDesktop).toBe(false) expect(result.current.isTablet).toBe(true) expect(result.current.isMobile).toBe(false) }) // Mobile act(() => { triggerResize(500) }) await waitFor(() => { expect(result.current.isDesktop).toBe(false) expect(result.current.isTablet).toBe(false) expect(result.current.isMobile).toBe(true) }) }) }) describe('isMobileOrTablet Helper', () => { it('should return true for mobile', async () => { setWindowWidth(500) const { result } = renderHook(() => useResponsive()) await waitFor(() => { expect(result.current.isMobileOrTablet).toBe(true) }) }) it('should return true for tablet', async () => { setWindowWidth(800) const { result } = renderHook(() => useResponsive()) await waitFor(() => { expect(result.current.isMobileOrTablet).toBe(true) }) }) it('should return false for desktop', async () => { setWindowWidth(1440) const { result } = renderHook(() => useResponsive()) await waitFor(() => { expect(result.current.isMobileOrTablet).toBe(false) }) }) }) describe('Boundary Conditions', () => { it('should handle exact breakpoint values correctly', async () => { const { result } = renderHook(() => useResponsive()) // Exactly at mobile breakpoint should be tablet act(() => { triggerResize(768) }) await waitFor(() => { expect(result.current.viewMode).toBe('tablet') }) // One pixel below mobile breakpoint should be mobile act(() => { triggerResize(767) }) await waitFor(() => { expect(result.current.viewMode).toBe('mobile') }) // Exactly at tablet breakpoint should be desktop act(() => { triggerResize(1024) }) await waitFor(() => { expect(result.current.viewMode).toBe('desktop') }) // One pixel below tablet breakpoint should be tablet act(() => { triggerResize(1023) }) await waitFor(() => { expect(result.current.viewMode).toBe('tablet') }) }) it('should handle very small widths', async () => { setWindowWidth(320) const { result } = renderHook(() => useResponsive()) await waitFor(() => { expect(result.current.viewMode).toBe('mobile') expect(result.current.windowWidth).toBe(320) }) }) it('should handle very large widths', async () => { setWindowWidth(2560) const { result } = renderHook(() => useResponsive()) await waitFor(() => { expect(result.current.viewMode).toBe('desktop') expect(result.current.windowWidth).toBe(2560) }) }) }) describe('Cleanup', () => { it('should remove resize listener on unmount', () => { const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener') const { unmount } = renderHook(() => useResponsive()) unmount() expect(removeEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function)) removeEventListenerSpy.mockRestore() }) it('should not cause memory leaks on multiple mount/unmount cycles', () => { const { unmount: unmount1 } = renderHook(() => useResponsive()) const { unmount: unmount2 } = renderHook(() => useResponsive()) const { unmount: unmount3 } = renderHook(() => useResponsive()) expect(() => { unmount1() unmount2() unmount3() }).not.toThrow() }) }) describe('Breakpoint Changes', () => { it('should update when breakpoints prop changes', async () => { const { result, rerender } = renderHook( ({ breakpoints }) => useResponsive(breakpoints), { initialProps: { breakpoints: DEFAULT_BREAKPOINTS, }, } ) setWindowWidth(800) triggerResize(800) await waitFor(() => { expect(result.current.viewMode).toBe('tablet') }) // Change breakpoints so 800 is now mobile rerender({ breakpoints: { mobile: 900, tablet: 1200, desktop: 1400, }, }) await waitFor(() => { expect(result.current.viewMode).toBe('mobile') }) }) }) describe('Multiple Resize Events', () => { it('should handle rapid resize events', async () => { const { result } = renderHook(() => useResponsive()) act(() => { triggerResize(1440) triggerResize(800) triggerResize(500) triggerResize(1200) }) await waitFor(() => { expect(result.current.windowWidth).toBe(1200) expect(result.current.viewMode).toBe('desktop') }) }) it('should maintain consistency during resize events', async () => { const { result } = renderHook(() => useResponsive()) for (let i = 0; i < 10; i++) { act(() => { triggerResize(400 + i * 100) }) } await waitFor(() => { // Final width should be 1300 (400 + 9*100) expect(result.current.windowWidth).toBe(1300) expect(result.current.viewMode).toBe('desktop') }) }) }) })